try...catch...finally的陷阱——加锁的线程开发经验分享

来源:互联网 发布:网络事件营销2017 编辑:程序博客网 时间:2024/05/16 19:32

本文出处:http://blog.csdn.net/chaijunkun/article/details/18318843,转载请注明。由于本人不定期会整理相关博文,会对相应内容作出完善。因此强烈建议在原始出处查看此文。


最近在忙一些数据处理的项目。为了方便操作,想把处理程序写成HTTP接口。需要的时候在浏览器里敲一下URL就可以执行相应功能。但是因为一个业务往往需要处理几个小时,等待HTTP返回是不现实的。于是把相关操作写成了一个线程,URL调用后异步处理。数据是按天操作的,而HTTP接口调用了之后因为网络状况不稳定可能会进行重试,如果对业务线程不加锁进行限制,多次调用接口会产生多个业务线程,造成各种问题。于是我建立了下面的模型,同时也遇到了一个关于try...catch...finally的陷阱,下面就跟大家分享一下。


首先创建一个存储任务名称和当前状态的HashMap,然后再建立插入、删除和查询当前任务的方法。由于是多线程操作的,需要在方法内对HashMap进行同步,代码如下:

package net.csdn.blog.chaijunkun.thread;import java.util.HashMap;import java.util.LinkedList;import java.util.List;import java.util.Map;import java.util.Set;import java.util.Map.Entry;import org.apache.log4j.Logger;import net.csdn.blog.chaijunkun.util.ObjectUtil;public class ThreadLocker {private static final Logger logger= Logger.getLogger(ThreadLocker.class); private static final Map<String, String> taskmap= new HashMap<String, String>();/** * 提交任务 * @param taskName 任务名称 * @param status 任务状态 * @return 发现重名提交失败 返回false 否则为true */public static boolean submitTask(String taskName, String status){synchronized (taskmap) {if (taskmap.containsKey(taskName)){return false;}else{taskmap.put(taskName, status);return true;}}}/** * 更新任务状态 * @param taskName 任务名称 * @param status 任务状态 * @return 无指定任务返回false 否则更新状态后返回true */public static boolean updateTask(String taskName, String status){synchronized (taskmap) {if (taskmap.containsKey(taskName)){taskmap.put(taskName, status);return true;}else{return false;}}}/** * 移除指定任务 * @param taskName 任务名称 */public static void removeTask(String taskName){synchronized (taskmap) {if (taskName.contains(taskName)){taskmap.remove(taskName);}}}/** * 列出当前正在执行的任务 * @return */public static List<String> listTask(){synchronized (taskmap) {if (ObjectUtil.isNotEmpty(taskmap)){Set<Entry<String, String>> entrySet= taskmap.entrySet();List<String> retVal= new LinkedList<String>();for (Entry<String, String> entry : entrySet) {retVal.add(String.format("任务:%s, 状态:%s", entry.getKey(), entry.getValue()));}return retVal;}else{return null;}}}public static void main(String[] args) {try {for(int i=0; i<10; i++){TestThread t= new TestThread(i);t.start();}List<String> taskList= ThreadLocker.listTask();if (ObjectUtil.isNotEmpty(taskList)){for (String taskInfo : taskList) {logger.info(taskInfo);}}Thread.sleep(10000L);} catch (InterruptedException e) {logger.error(e);}}}

任务名称在我的真实代码中采用“业务名称+日期”,本文中我采用固定的名称“lock_thread”,因此在上述DemoCode应该只能启动一个线程来处理业务。下面贴出业务线程的写法:

package net.csdn.blog.chaijunkun.thread;import org.apache.log4j.Logger;public class TestThread extends Thread {private static final Logger logger= Logger.getLogger(TestThread.class);private int id;private String getTaskName(){return "lock_thread";}public TestThread(int id){this.id= id;this.setName(this.getTaskName());}public void run(){String taskName= this.getName();try{//上锁if (!ThreadLocker.submitTask(taskName, String.format("示例线程, id:%d", this.id))){//logger.info(String.format("[id:%s][加锁失败]", this.id));return;}else{//logger.info(String.format("[id:%s][加锁成功]", this.id));}//线程要做的事情for(int i=0; i<20; i++){logger.info(String.format("[id:%s][print:%d]", this.id, i));Thread.sleep(1L);}} catch (Exception e) {logger.error(e);} finally{//解锁//logger.info(String.format("[id:%s][销毁]", this.id));ThreadLocker.removeTask(taskName);}}}

上述线程代码中,开始为了代码统一,我把上锁的代码放在了try中,之所以要采用try...catch...finally的写法是因为在业务处理过程中有多种错误发生不允许继续执行,因此我希望不管发生什么错误,最终都应该把锁解开。


好了,愿望是美好的,看看执行结果吧:

2014-01-15 19:29:22,538 INFO [lock_thread] - TestThread.run(32) | [id:6][print:0]2014-01-15 19:29:22,538 INFO [lock_thread] - TestThread.run(32) | [id:8][print:0]2014-01-15 19:29:22,538 INFO [lock_thread] - TestThread.run(32) | [id:0][print:0]2014-01-15 19:29:22,538 INFO [lock_thread] - TestThread.run(32) | [id:1][print:0]2014-01-15 19:29:22,538 INFO [lock_thread] - TestThread.run(32) | [id:5][print:0]2014-01-15 19:29:22,585 INFO [lock_thread] - TestThread.run(32) | [id:5][print:1]2014-01-15 19:29:22,586 INFO [lock_thread] - TestThread.run(32) | [id:1][print:1]2014-01-15 19:29:22,586 INFO [lock_thread] - TestThread.run(32) | [id:8][print:1]2014-01-15 19:29:22,586 INFO [lock_thread] - TestThread.run(32) | [id:0][print:1]2014-01-15 19:29:22,586 INFO [lock_thread] - TestThread.run(32) | [id:6][print:1]2014-01-15 19:29:22,587 INFO [lock_thread] - TestThread.run(32) | [id:1][print:2]2014-01-15 19:29:22,587 INFO [lock_thread] - TestThread.run(32) | [id:5][print:2]2014-01-15 19:29:22,595 INFO [lock_thread] - TestThread.run(32) | [id:0][print:2]2014-01-15 19:29:22,595 INFO [lock_thread] - TestThread.run(32) | [id:6][print:2]2014-01-15 19:29:22,595 INFO [lock_thread] - TestThread.run(32) | [id:8][print:2]2014-01-15 19:29:22,596 INFO [lock_thread] - TestThread.run(32) | [id:0][print:3]2014-01-15 19:29:22,596 INFO [lock_thread] - TestThread.run(32) | [id:5][print:3].....

坑爹了,居然没锁住,线程全起来了。为什么会这样!!!后来我把解锁代码放到了finally的外面,或者把加锁代码放到了try外面:

public void run(){String taskName= this.getName();//上锁if (!ThreadLocker.submitTask(taskName, String.format("示例线程, id:%d", this.id))){//logger.info(String.format("[id:%s][加锁失败]", this.id));return;}else{//logger.info(String.format("[id:%s][加锁成功]", this.id));}try{//线程要做的事情for(int i=0; i<20; i++){logger.info(String.format("[id:%s][print:%d]", this.id, i));Thread.sleep(1L);}} catch (Exception e) {logger.error(e);} finally{//解锁//logger.info(String.format("[id:%s][销毁]", this.id));ThreadLocker.removeTask(taskName);}}

居然正常了:

2014-01-15 19:34:26,239 INFO [lock_thread] - TestThread.run(32) | [id:3][print:0]2014-01-15 19:34:26,245 INFO [lock_thread] - TestThread.run(32) | [id:3][print:1]2014-01-15 19:34:26,246 INFO [lock_thread] - TestThread.run(32) | [id:3][print:2]2014-01-15 19:34:26,247 INFO [lock_thread] - TestThread.run(32) | [id:3][print:3]2014-01-15 19:34:26,248 INFO [lock_thread] - TestThread.run(32) | [id:3][print:4]2014-01-15 19:34:26,249 INFO [lock_thread] - TestThread.run(32) | [id:3][print:5]2014-01-15 19:34:26,250 INFO [lock_thread] - TestThread.run(32) | [id:3][print:6]2014-01-15 19:34:26,251 INFO [lock_thread] - TestThread.run(32) | [id:3][print:7]2014-01-15 19:34:26,254 INFO [lock_thread] - TestThread.run(32) | [id:3][print:8]2014-01-15 19:34:26,255 INFO [lock_thread] - TestThread.run(32) | [id:3][print:9]2014-01-15 19:34:26,256 INFO [lock_thread] - TestThread.run(32) | [id:3][print:10]2014-01-15 19:34:26,257 INFO [lock_thread] - TestThread.run(32) | [id:3][print:11]2014-01-15 19:34:26,258 INFO [lock_thread] - TestThread.run(32) | [id:3][print:12]2014-01-15 19:34:26,259 INFO [lock_thread] - TestThread.run(32) | [id:3][print:13]2014-01-15 19:34:26,260 INFO [lock_thread] - TestThread.run(32) | [id:3][print:14]2014-01-15 19:34:26,261 INFO [lock_thread] - TestThread.run(32) | [id:3][print:15]2014-01-15 19:34:26,264 INFO [lock_thread] - TestThread.run(32) | [id:3][print:16]2014-01-15 19:34:26,265 INFO [lock_thread] - TestThread.run(32) | [id:3][print:17]2014-01-15 19:34:26,266 INFO [lock_thread] - TestThread.run(32) | [id:3][print:18]2014-01-15 19:34:26,267 INFO [lock_thread] - TestThread.run(32) | [id:3][print:19]


不断查找问题的根源。在开始的代码中将TestThread的log解注释后,执行貌似也正常了,但是这应该不是bug的根源。


后来仔细研究了一下try...catch...finally的执行逻辑。在try代码块中的return返回后会执行finally中的代码块,这谁都知道。但是由于一时糊涂,把加锁代码放在了try里面,当发现重名任务无法提交时,线程本应该直接退出,并且不应该解锁,但事实上return后也执行了finally中的解锁逻辑,因此出现了看起来加锁无效的bug。看来以后写代码也不能光图好看了,也要注意隐含的陷阱。

0 0
原创粉丝点击