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