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

转载 2016年08月29日 10:58:40

本文出处: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。看来以后写代码也不能光图好看了,也要注意隐含的陷阱。

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

最近在忙一些数据处理的项目。为了方便操作,想把处理程序写成HTTP接口。需要的时候在浏览器里敲一下URL就可以执行相应功能。但是因为一个业务往往需要处理几个小时,等待HTTP返回是不现实的。于是把相关...
  • chaijunkun
  • chaijunkun
  • 2014年01月15日 19:42
  • 6087

Java多线程——加锁机制

Java的锁分为内置锁和显式锁。内置锁在我们平时使用synchronized关键字的时候获取。显式锁则是通过获取java.util.concurrent.locks包下面的ReentrantLock类...
  • sakurairo_maukoro
  • sakurairo_maukoro
  • 2015年03月08日 19:54
  • 2278

多线程的几种加锁方式详解

NSLock NSLock是Cocoa提供给我们最基本的锁对象,这也是我们经常使用的,除lock和unlock外,NSLock还提供了tryLock和lockBeforeDate:两个方法,前一...
  • qq_35247219
  • qq_35247219
  • 2016年07月17日 09:47
  • 20076

线程编程之:加锁解锁最简单例子

//  pthread_mutex_t BackUpKey;                           main头上应该定义该变量 //    pthread_mutex_init(&Ba...
  • cwj649956781
  • cwj649956781
  • 2012年08月14日 09:27
  • 1564

Java 多线程加锁的方式总结及对比

参考博文:http://www.cnblogs.com/handsomeye/p/5999362.html 一.Java多线程可以通过: 1. synchronized关键字 2. Ja...
  • u010842515
  • u010842515
  • 2017年03月28日 14:27
  • 4614

synchronized、锁、多线程同步的原理是咋样

先综述个结论: 一般说的synchronized用来做多线程同步功能,其实synchronized只是提供多线程互斥,而对象的wait()和notify()方法才提供线程的同步功能。 ...
  • jks456
  • jks456
  • 2016年03月05日 21:54
  • 2541

诡异的线程加锁问题

引言:在Java中,对于互斥的代码块,我们需要使用synchronized来进行线程安全的保证,本文将针对某个synchronized的锁实例中发生的问题来分析。...
  • blueheart20
  • blueheart20
  • 2016年09月19日 22:12
  • 616

在一个线程加锁,另一个线程解锁

一般来讲,一个线程加锁,另一个线程解锁,是很容易死锁的。 产生死锁的四个必要条件: (1) 互斥条件:一个资源每次只能被一个进程使用。 (2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资...
  • p2016
  • p2016
  • 2017年08月03日 15:03
  • 517

多线程程序何时需要加锁

简单的说三条: 多人读,不需要 一读一写要加 多人写要加 常见错误 1读1写没事,读写的内存约多,越容易出事,因为不是原子操作 对int/int64/char型是原子操作, 可不加...
  • ma100
  • ma100
  • 2016年06月12日 14:01
  • 2128

C++中多线程的加锁机制

问题来源于某面试题: 编写一个单例模式的类。 #include #include #include using namespace std; class singleStance{ privat...
  • a342374071
  • a342374071
  • 2014年01月14日 20:08
  • 13015
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:try...catch...finally的陷阱——加锁的线程开发经验分享
举报原因:
原因补充:

(最多只允许输入30个字)