1. 程式人生 > >Java多執行緒程式非阻塞式鎖定實現

Java多執行緒程式非阻塞式鎖定實現

           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);
	}
}

      通過以上設計,系統內部任何耗時且需要保證單執行緒的地方都可以用該方法實現非阻塞式的訪問,提高使用者體驗。甚至於有的呼叫本身就要求這樣的設計,只需處理一次,比如做日終。鎖名的自定義帶來了鎖粒度的靈活設定,可以在執行時根據引數實現任意級別的鎖定。