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

来源:互联网 发布:宾得k3 知乎 编辑:程序博客网 时间:2024/04/30 02:03

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


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


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

[java] view plain copy 在CODE上查看代码片派生到我的代码片
  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应该只能启动一个线程来处理业务。下面贴出业务线程的写法:

[java] view plain copy 在CODE上查看代码片派生到我的代码片
  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的写法是因为在业务处理过程中有多种错误发生不允许继续执行,因此我希望不管发生什么错误,最终都应该把锁解开。


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

[plain] view plain copy 在CODE上查看代码片派生到我的代码片
  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外面:

[java] view plain copy 在CODE上查看代码片派生到我的代码片
  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. }  

居然正常了:

[plain] view plain copy 在CODE上查看代码片派生到我的代码片
  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。看来以后写代码也不能光图好看了,也要注意隐含的陷阱。

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 当发生灾害或突发事故时怎么办 物流信息和和收货地址不一样怎么办 小孩手被电梯门夹了怎么办 车管所50选一没看中的号码怎么办 长沙芙蓉区电动车被交警扣了怎么办 驾照正在考的时候住址变了怎么办 身份证到期了驾驶证上的号码怎么办 小车占道跟大车刮交警怎么办 邻居把消防栓的位置占用了怎么办 查环保要停业整改一个月怎么办 抽油烟机管道公共排烟道漏烟怎么办 深圳龙华电动车被交警扣了怎么办 英国平邮寄到中国丢件怎么办 安卓app与设备不兼容怎么办 手机卡坏了收不到快递短信怎么办 pph手术后钛钉没有脱落怎么办 右侧附件囊状透明声可怎么办 刚刚出生小孩睾丸没有掉下来怎么办 汽车被依非法营运之由扣押怎么办 老婆结婚小孩两个又上夜场该怎么办 对于不断无理要求赔偿的房东怎么办 欠的钱越来越多不知道怎么办了 手机壳的开机按键不好按怎么办 对方把保权的房子卖给我怎么办 内存卡在手机上显示损坏怎么办 内存卡显示但是下载不了东西怎么办 相机sd卡没办法读卡怎么办 尼康相机新sd卡显示满了怎么办 修冰箱没几个月又坏了怎么办 星露谷物语活动中心成了仓库怎么办 你家如果油锅起火时应该怎么办 当你遇到电器起火时应该怎么办 酷派大神f2充不进去电怎么办 借车子别人开违章一年了怎么办 快递刚发货我点错了收货怎么办 快递收货地址错了已经发货了怎么办 老公在外地工地上夜班吃不好怎么办 工人在工地干活被吊车撞了怎么办 小米手机记不得保密柜密码怎么办 华为手机文件保密柜密码忘了怎么办 超市寄存柜密码纸丢了怎么办