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

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


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


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

  1. package net.csdn.blog.chaijunkun.thread;  
  2.   
  3. import java.util.HashMap;  
  4. import java.util.LinkedList;  
  5. import java.util.List;  
  6. import java.util.Map;  
  7. import java.util.Set;  
  8. import java.util.Map.Entry;  
  9.   
  10. import org.apache.log4j.Logger;  
  11.   
  12. import net.csdn.blog.chaijunkun.util.ObjectUtil;  
  13.   
  14. public class ThreadLocker {  
  15.       
  16.     private static final Logger logger= Logger.getLogger(ThreadLocker.class);   
  17.       
  18.     private static final Map<String, String> taskmap= new HashMap<String, String>();  
  19.       
  20.     /** 
  21.      * 提交任务 
  22.      * @param taskName 任务名称 
  23.      * @param status 任务状态 
  24.      * @return 发现重名提交失败 返回false 否则为true 
  25.      */  
  26.     public static boolean submitTask(String taskName, String status){  
  27.         synchronized (taskmap) {  
  28.             if (taskmap.containsKey(taskName)){  
  29.                 return false;  
  30.             }else{  
  31.                 taskmap.put(taskName, status);  
  32.                 return true;  
  33.             }  
  34.         }  
  35.     }  
  36.       
  37.     /** 
  38.      * 更新任务状态 
  39.      * @param taskName 任务名称 
  40.      * @param status 任务状态 
  41.      * @return 无指定任务返回false 否则更新状态后返回true 
  42.      */  
  43.     public static boolean updateTask(String taskName, String status){  
  44.         synchronized (taskmap) {  
  45.             if (taskmap.containsKey(taskName)){  
  46.                 taskmap.put(taskName, status);  
  47.                 return true;  
  48.             }else{  
  49.                 return false;  
  50.             }  
  51.         }  
  52.     }  
  53.       
  54.     /** 
  55.      * 移除指定任务 
  56.      * @param taskName 任务名称 
  57.      */  
  58.     public static void removeTask(String taskName){  
  59.         synchronized (taskmap) {  
  60.             if (taskName.contains(taskName)){  
  61.                 taskmap.remove(taskName);  
  62.             }  
  63.         }  
  64.     }  
  65.       
  66.     /** 
  67.      * 列出当前正在执行的任务 
  68.      * @return 
  69.      */  
  70.     public static List<String> listTask(){  
  71.         synchronized (taskmap) {  
  72.             if (ObjectUtil.isNotEmpty(taskmap)){  
  73.                 Set<Entry<String, String>> entrySet= taskmap.entrySet();  
  74.                 List<String> retVal= new LinkedList<String>();  
  75.                 for (Entry<String, String> entry : entrySet) {  
  76.                     retVal.add(String.format("任务:%s, 状态:%s", entry.getKey(), entry.getValue()));  
  77.                 }  
  78.                 return retVal;  
  79.             }else{  
  80.                 return null;  
  81.             }  
  82.         }  
  83.     }  
  84.       
  85.     public static void main(String[] args) {  
  86.         try {  
  87.             for(int i=0; i<10; i++){  
  88.                 TestThread t= new TestThread(i);  
  89.                 t.start();  
  90.             }  
  91.             List<String> taskList= ThreadLocker.listTask();  
  92.             if (ObjectUtil.isNotEmpty(taskList)){  
  93.                 for (String taskInfo : taskList) {  
  94.                     logger.info(taskInfo);  
  95.                 }  
  96.             }  
  97.             Thread.sleep(10000L);  
  98.         } catch (InterruptedException e) {  
  99.             logger.error(e);  
  100.         }  
  101.     }  
  102.   
  103. }  

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

  1. package net.csdn.blog.chaijunkun.thread;  
  2.   
  3. import org.apache.log4j.Logger;  
  4.   
  5. public class TestThread extends Thread {  
  6.       
  7.     private static final Logger logger= Logger.getLogger(TestThread.class);   
  8.       
  9.     private int id;  
  10.       
  11.     private String getTaskName(){  
  12.         return "lock_thread";  
  13.     }  
  14.       
  15.     public TestThread(int id){  
  16.         this.id= id;  
  17.         this.setName(this.getTaskName());  
  18.     }  
  19.       
  20.     public void run(){  
  21.         String taskName= this.getName();  
  22.         try{  
  23.             //上锁  
  24.             if (!ThreadLocker.submitTask(taskName, String.format("示例线程, id:%d"this.id))){  
  25.                 //logger.info(String.format("[id:%s][加锁失败]", this.id));  
  26.                 return;  
  27.             }else{  
  28.                 //logger.info(String.format("[id:%s][加锁成功]", this.id));  
  29.             }  
  30.             //线程要做的事情  
  31.             for(int i=0; i<20; i++){  
  32.                 logger.info(String.format("[id:%s][print:%d]"this.id, i));  
  33.                 Thread.sleep(1L);  
  34.             }  
  35.         } catch (Exception e) {  
  36.             logger.error(e);  
  37.         } finally{  
  38.             //解锁  
  39.             //logger.info(String.format("[id:%s][销毁]", this.id));  
  40.             ThreadLocker.removeTask(taskName);  
  41.         }  
  42.     }  
  43.   
  44. }  

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


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

  1. 2014-01-15 19:29:22,538 INFO [lock_thread] - TestThread.run(32) | [id:6][print:0]  
  2. 2014-01-15 19:29:22,538 INFO [lock_thread] - TestThread.run(32) | [id:8][print:0]  
  3. 2014-01-15 19:29:22,538 INFO [lock_thread] - TestThread.run(32) | [id:0][print:0]  
  4. 2014-01-15 19:29:22,538 INFO [lock_thread] - TestThread.run(32) | [id:1][print:0]  
  5. 2014-01-15 19:29:22,538 INFO [lock_thread] - TestThread.run(32) | [id:5][print:0]  
  6. 2014-01-15 19:29:22,585 INFO [lock_thread] - TestThread.run(32) | [id:5][print:1]  
  7. 2014-01-15 19:29:22,586 INFO [lock_thread] - TestThread.run(32) | [id:1][print:1]  
  8. 2014-01-15 19:29:22,586 INFO [lock_thread] - TestThread.run(32) | [id:8][print:1]  
  9. 2014-01-15 19:29:22,586 INFO [lock_thread] - TestThread.run(32) | [id:0][print:1]  
  10. 2014-01-15 19:29:22,586 INFO [lock_thread] - TestThread.run(32) | [id:6][print:1]  
  11. 2014-01-15 19:29:22,587 INFO [lock_thread] - TestThread.run(32) | [id:1][print:2]  
  12. 2014-01-15 19:29:22,587 INFO [lock_thread] - TestThread.run(32) | [id:5][print:2]  
  13. 2014-01-15 19:29:22,595 INFO [lock_thread] - TestThread.run(32) | [id:0][print:2]  
  14. 2014-01-15 19:29:22,595 INFO [lock_thread] - TestThread.run(32) | [id:6][print:2]  
  15. 2014-01-15 19:29:22,595 INFO [lock_thread] - TestThread.run(32) | [id:8][print:2]  
  16. 2014-01-15 19:29:22,596 INFO [lock_thread] - TestThread.run(32) | [id:0][print:3]  
  17. 2014-01-15 19:29:22,596 INFO [lock_thread] - TestThread.run(32) | [id:5][print:3]  
  18. .....  

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

  1. public void run(){  
  2.     String taskName= this.getName();  
  3.     //上锁  
  4.     if (!ThreadLocker.submitTask(taskName, String.format("示例线程, id:%d"this.id))){  
  5.         //logger.info(String.format("[id:%s][加锁失败]", this.id));  
  6.         return;  
  7.     }else{  
  8.         //logger.info(String.format("[id:%s][加锁成功]", this.id));  
  9.     }  
  10.     try{  
  11.         //线程要做的事情  
  12.         for(int i=0; i<20; i++){  
  13.             logger.info(String.format("[id:%s][print:%d]"this.id, i));  
  14.             Thread.sleep(1L);  
  15.         }  
  16.     } catch (Exception e) {  
  17.         logger.error(e);  
  18.     } finally{  
  19.         //解锁  
  20.         //logger.info(String.format("[id:%s][销毁]", this.id));  
  21.         ThreadLocker.removeTask(taskName);  
  22.     }  
  23. }  

居然正常了:

  1. 2014-01-15 19:34:26,239 INFO [lock_thread] - TestThread.run(32) | [id:3][print:0]  
  2. 2014-01-15 19:34:26,245 INFO [lock_thread] - TestThread.run(32) | [id:3][print:1]  
  3. 2014-01-15 19:34:26,246 INFO [lock_thread] - TestThread.run(32) | [id:3][print:2]  
  4. 2014-01-15 19:34:26,247 INFO [lock_thread] - TestThread.run(32) | [id:3][print:3]  
  5. 2014-01-15 19:34:26,248 INFO [lock_thread] - TestThread.run(32) | [id:3][print:4]  
  6. 2014-01-15 19:34:26,249 INFO [lock_thread] - TestThread.run(32) | [id:3][print:5]  
  7. 2014-01-15 19:34:26,250 INFO [lock_thread] - TestThread.run(32) | [id:3][print:6]  
  8. 2014-01-15 19:34:26,251 INFO [lock_thread] - TestThread.run(32) | [id:3][print:7]  
  9. 2014-01-15 19:34:26,254 INFO [lock_thread] - TestThread.run(32) | [id:3][print:8]  
  10. 2014-01-15 19:34:26,255 INFO [lock_thread] - TestThread.run(32) | [id:3][print:9]  
  11. 2014-01-15 19:34:26,256 INFO [lock_thread] - TestThread.run(32) | [id:3][print:10]  
  12. 2014-01-15 19:34:26,257 INFO [lock_thread] - TestThread.run(32) | [id:3][print:11]  
  13. 2014-01-15 19:34:26,258 INFO [lock_thread] - TestThread.run(32) | [id:3][print:12]  
  14. 2014-01-15 19:34:26,259 INFO [lock_thread] - TestThread.run(32) | [id:3][print:13]  
  15. 2014-01-15 19:34:26,260 INFO [lock_thread] - TestThread.run(32) | [id:3][print:14]  
  16. 2014-01-15 19:34:26,261 INFO [lock_thread] - TestThread.run(32) | [id:3][print:15]  
  17. 2014-01-15 19:34:26,264 INFO [lock_thread] - TestThread.run(32) | [id:3][print:16]  
  18. 2014-01-15 19:34:26,265 INFO [lock_thread] - TestThread.run(32) | [id:3][print:17]  
  19. 2014-01-15 19:34:26,266 INFO [lock_thread] - TestThread.run(32) | [id:3][print:18]  
  20. 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。看来以后写代码也不能光图好看了,也要注意隐含的陷阱。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值