1. 程式人生 > >使用CAS實現一個超時鎖

使用CAS實現一個超時鎖

原子 clas zed null 參數 退出 area 流程 exp

背景

最近做的項目有這樣一個需求,我們有一個問題記錄,每一個問題記錄有一個整改人員字段,這個整改人員是可以有多個人的。整改人員可以對這個問題進行整改,但是業務要求同時只能有一個整改人可以進入整改頁面,當有一個整改者在進行整改時,提示當前有另一個整改者在整改,請稍後。

解決方案

最開始我想使用一個全局鎖來解決這個問題,但是因為用戶操作的不可控行,機器的意外故障等原因可能使得用戶加鎖後但是沒有釋放鎖,這樣會導致發生死鎖問題。於是我打算實現一個可以設置過期時間的鎖,這樣就能解決死鎖的問題。但是這樣會存在一個問題,就是首先A整改者獲取鎖進入整改頁面,但是遲遲沒有提交問題記錄並且釋放鎖,這個時候因為鎖過期了,有另外一個B整改者獲取鎖進入整改頁面,這樣就有兩個整改者在整改了。但是可以在提交整改成果時結合數據庫的樂觀鎖保證只有一個用戶能夠修改成功,也就是判斷下此問題記錄的修改時間,如果修改時間在整改期間內沒有變化過,說明沒有別的整改者提交這個問題,如果不相等,說明有別的整改者整改了,需要退出重新獲取鎖進入整改頁面整改。

因為JDK提供的synchronized關鍵字以及Lock組件都沒有鎖過期的功能,於是我使用CAS實現了一個可以設置過期時間的鎖。該鎖必須滿足以下幾點:

  • 同一時刻只能有一個用戶獲取鎖(鎖是有效的, 沒有過期)。

  • 鎖需要有一個過期時間, 避免發生死鎖。

  • 只有擁有鎖的用戶才能釋放鎖。

本來想畫個流程圖的,但是代碼其實也很簡單,看下就懂了

public class BusinessLock {

    private static class LockObj {

        /**
         * 擁有鎖的用戶
         */
        String owner;

        /**
         * 鎖的過期時間
         */
        long expireTime;

        LockObj(String owner, long expireTime) {
            this.owner = owner;
            this.expireTime = expireTime;
        }
    }

    private AtomicReference<LockObj> lockObjReference = new AtomicReference<>();

    /**
     * 嘗試獲取鎖
     * @param owner 鎖的標識
     * @param expireTime 鎖的過期時間(單位為秒)
     * @return 獲取鎖成功返回true, 否則返回false
     */
    public boolean tryLock(String owner, long expireTime) {
        LockObj lockObj = new LockObj(owner, System.currentTimeMillis() + 
                                      expireTime * 1000);
        // 獲取鎖成功
        if (lockObjReference.compareAndSet(null, lockObj)) {
            return true;
        }
        // 判斷鎖是否失效, 避免發生死鎖, 如果失效再次嘗試獲取鎖.
        LockObj oldLockObj = lockObjReference.get();
        return (oldLockObj == null || oldLockObj.expireTime < System.currentTimeMillis())
                && lockObjReference.compareAndSet(oldLockObj, lockObj);
    }

    /**
     * 釋放鎖
     * 如果返回值為false, 說明當前用戶沒有擁有該鎖, 或者曾經擁有鎖但是因為鎖過期而被別的用戶獲取了
     * @param owner 擁有鎖的用戶
     * @return 釋放鎖成功返回true, 否則返回false
     */
    public boolean unLock(String owner) {
        LockObj currentLockObj = lockObjReference.get();
        // 如果鎖的擁有者是owner參數指定的用戶, 才能解鎖
        // 不能直接用set方法, 需要原子更新, 因為有可能鎖過期突然被別的用戶獲取了
        return currentLockObj != null && currentLockObj.owner.equals(owner)
                && lockObjReference.compareAndSet(currentLockObj, null);
    }
}

使用CAS實現一個超時鎖