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

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

  1. java synoch 加锁_线程间通信 - HappyCowboy - 博客园

    线程之间需要一些协调通信,来共同完成一件任务.Java多线程中,线程之间通信最常用的两个方法是wait()与notify() 使用wait()与notify()实现线程间的通信,需注意: ①wait( ...

  2. ReentrantLock加锁与释放锁分享

    Lock源码分享 创建锁需要什么? 1.标识--标识当前对象锁是否被获取,以及锁获取了多少次,比如当前锁是否允许可重入 2. 当我们构造ReentrantLock对象是,有两种构造方法 默认为非公平锁 ...

  3. JAVA如何在线程中加锁(四种方法)

    JAVA多线程锁 线程的生命周期 ​ 总共六种状态,可归结为五种,线程的最终是死亡,阻塞不是最终状态,只是一个临时状态.只有调用了start方法,线程才进入就绪阶段. //新生 ​ NEW, //运行 ...

  4. 【Java 集合】Java 集合的线程安全性 ( 加锁同步 | java.utils 集合 | 集合属性 | java.util.concurrent 集合 | CopyOnWrite 机制 )

    文章目录 I . Java 集合的线程安全概念 ( 加锁同步 ) II . 线程不安全集合 ( 没有并发需求 推荐使用 ) III . 集合属性说明 IV . 早期的线程安全集合 ( 不推荐使用 ) ...

  5. 线程同步之互斥量加锁解锁 死锁

    与互斥锁相关API       互斥量(mutex)从本质上来说是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量上的锁.对互斥量进行加锁后,任何其他试图再次对互斥量加锁的线程将会被阻 ...

  6. java 线程重入,java synchronized加载加锁-线程可重入详解及实例代码

    java synchronized加载加锁-线程可重入 实例代码: public class ReGetLock implements Runnable { @Override public void ...

  7. 它又来了!C**HashMap是如何保证线程安全的?会用不就完了?

    欢迎关注方志朋的博客,回复"666"获面试宝典 阅读此篇文章,你需要有以下知识基础 Java内存模型,可见性问题 CAS HashMap底层原理 我们知道,在日常开发中使用的Has ...

  8. java 线程关闭小结(转)

    首先,要说的是java中没有一种停止线程的方法是绝对安全的. 线程的中断 Thread.interrput()方法很容易给人一种误会,让人感觉是一个线程使另外一个正在运行的线程停止工作, 但实际上in ...

  9. 今天,进程告诉我线程它它它它不想活了

    来自:Java建设者 上一篇文章我们解剖了进程和线程的本质,进程和线程的实现方式,这篇文章我们来探讨它们是如何通信的,进程告诉我说线程不想活了,我不管它死活,我只想知道我是谁?进程是怎么告诉我的?进程 ...

最新文章

  1. 信息上传服务器加速cpu处理,英特尔发布全新第二代至强可扩展处理器携手浪潮加速新型应用发展...
  2. C++ 类的大小计算
  3. faster rcnn论文_faster-rcnn论文思路及代码编译
  4. http如何像tcp一样实时的收消息?
  5. 编译Bitcoin BCH configure: error: libdb_cxx headers missing ,终于解决了
  6. Linux网络流量监控Iftop安装
  7. chromium 桌面_如何使用Chromium和PyInstaller将Web应用程序转换为桌面应用程序
  8. 计算机二级vb资料百度云,全国计算机等级考试二级VB复习资料.pdf
  9. box-sizing 的作用
  10. android dns 测试工具,DNS Test测速工具
  11. 【java】读取html乱码
  12. 阿里云云大使经验分享:老司机9招带你玩转云大使推广
  13. 计算机无法删除ie,Win7系统IE11 IE10 IE9强制卸载工具方法(解决IE无法卸载)
  14. HF-LPX70_SDK提示. sh: 1: ././tools/lzma: not found 解决办法
  15. OceanBase数据库实践入门——手动搭建OceanBase集群
  16. Calendar获取日期所在周、月份第一天、最后一天以及前一周内所有时间
  17. leetcode:1276. 不浪费原料的汉堡制作方案(贪心)
  18. 讲的真详细!如何成为一个更好的Android开发者?成功入职腾讯
  19. 工具分享:linux中的rar解压安装包(tar)请自行下载(附下载链接)
  20. Python调用纷享销客CRM开放平台API

热门文章

  1. 通过反编译让SpecFlow支持多层属性值的验证
  2. 代码重构(四):条件表达式重构规则
  3. oracle数据库服务器启动后需执行的命令(SecureCRT中执行)
  4. Geodatabase模型
  5. 51nod1245 Binomial Coefficients Revenge
  6. [学习笔记] Cordova+AmazeUI+React 做个通讯录 - 使用 SQLite
  7. 【思路题】【多校第一场】【1001.OO’s Sequence】
  8. 关于keil c51与keil mdk同时安装、合并解决办法
  9. IOS 学习笔记(3) 视图UITabbarController
  10. 全栈开发工程师微信小程序-上(中)