您的位置:首页 > 编程语言 > Java开发

Java多线程程序非阻塞式锁定实现

2012-06-19 14:22 344 查看
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);
	}
}


通过以上设计,系统内部任何耗时且需要保证单线程的地方都可以用该方法实现非阻塞式的访问,提高用户体验。甚至于有的调用本身就要求这样的设计,只需处理一次,比如做日终。锁名的自定义带来了锁粒度的灵活设定,可以在运行时根据参数实现任意级别的锁定。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: