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