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