Java对多线程程序的锁定已经有良好的支持,通常使用synchronized修饰一个方法或者一段代码。但是有一个问题,多个线程同时调用同一个方法的时候,所有线程都被排队处理了。该被调用的方法越耗时,线程越多的时候,等待的线程等待的时间也就越长,甚至于几分钟或者几十分钟。对于Web等对反应时间要求很高的系统来说,这是不可以接受的。本文就介绍一种自己实现的锁定方法,可以在没有拿到锁之后马上返回,告诉客户稍后重试。
某一段程序同一时刻需要保证只能单线程调用,那么策略很简单,最先到的线程获取锁成功,在它释放之前其它线程都会获取失败。首先要构造一个全局的系统锁仓库,代码如下:
/*
* LockStore.java 2012-5-15
*/
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* 公用的内存锁仓库. 分为获取锁和释放锁两种操作。
*
* @version 1.0
*/
public final class LockStore {
// volatile保证所有线程看到的锁相同
private static volatile Map<String, Date> locks = new HashMap<String, Date>();
private LockStore() {
}
/**
* 根据锁名获取锁
*
* @param lockName
* 锁名
* @return 是否锁定成功
*/
public synchronized static Boolean getLock(String lockName) {
Boolean locked = false;
if (StringUtils.isEmpty(lockName)) {
throw new RuntimeException("Lock name can't be empty");
}
Date lockDate = locks.get(lockName);
if (lockDate == null) {
locks.put(lockName, new Date());
locked = true;
}
return locked;
}
/**
* 根据锁名释放锁
*
* @param lockName
* 锁名
*/
public synchronized static void releaseLock(String lockName) {
if (StringUtils.isEmpty(lockName)) {
throw new RuntimeException("Lock name can't be empty");
}
Date lockDate = locks.get(lockName);
if (lockDate != null) {
locks.remove(lockName);
}
}
/**
* 获取上次成功锁定的时间
*
* @param lockName
* 锁名
* @return 如果还没有锁定返回NULL
*/
public synchronized static Date getLockDate(String lockName) {
if (StringUtils.isEmpty(lockName)) {
throw new RuntimeException("Lock name can't be empty");
}
Date lockDate = locks.get(lockName);
return lockDate;
}
}
锁仓库提供了三个方法,都是静态的,可以在系统内任意地方调用。 这里要提的是锁名,是一个字符串,可以随意构造,通常是需要锁定的方法名+需要单线程处理的标识。比如部门ID。这样不同的部门有不同的锁,独立运行,同一个部门同一个锁,单线程处理。具体使用如下:
/*
* LockTest.java 2012-6-19
*/
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 锁仓库的使用
*
* @version 1.0
*/
public class LockTest {
public Boolean doSomething(String departmentId, StringBuffer message) {
// 同一个部门同时只能有一个处理, 不同部门可以并行处理
String lockName = "doSomething_" + departmentId;
Boolean result;
if (LockStore.getLock(lockName)) {
try {
// do things here
} finally {
LockStore.releaseLock(lockName);
result = true;
}
} else {
Date lastLockDate = LockStore.getLockDate(lockName);
String messageStr = "您所在的部门已经在处理中, 启动时间为:"
+ getDateDetailDesc(lastLockDate);
message.append(messageStr);
result = false;
}
return result;
}
/*
* 获取日期的具体时间描述
*/
private String getDateDetailDesc(Date date) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
return sdf.format(date);
}
}
通过以上设计,系统内部任何耗时且需要保证单线程的地方都可以用该方法 实现非阻塞式的访问,提高用户体验。甚至于有的调用本身就要求这样的设计,只需处理一次,比如做日终。锁名的自定义带来了锁粒度的灵活设定,可以在运行时根据参数实现任意级别的锁定。