深入理解 ReentrantLock

TimberLiu發表於2019-04-14

ReentrantLock

ReentrantLock 是一種可重入鎖,它指的是一個執行緒能夠對資源重複加鎖。ReentrantLocksynchronized 類似,能夠保證解決執行緒安全問題,但是卻提供了比 synchronized 更強大、靈活的機制,例如可中斷式的獲取鎖、可定時的獲取鎖等。

另外,ReentrantLock 也提供了公平鎖與非公平鎖的選擇,它們之間的區別主要就是看對鎖的獲取與獲取鎖的請求的順序是否是一致的,選擇公平鎖時,等待時間最長的執行緒會最優先獲取到鎖,但是公平鎖獲取的效率通常比非公平鎖要低。可以在構造方法中通過傳參的方式來具體指定選擇公平或非公平。

公平鎖

ReentrantLock 中,有一個抽象內部類 Sync,它繼承自 AQSReentrantLock 的大部分功能都委託給 Sync 進行實現,其內部定義了 lock() 抽象方法,預設實現了 nonfairTryAcquire() 方法,它是非公平鎖的預設實現。

Sync 有兩個子類:公平鎖 FairSyncNonFairSync,實現了 Sync 中的 lock() 方法和 AQS 中的 tryAcquire() 方法。

NonFairSync

NonFairSynclock() 方法的實現如下:

final void lock() {
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}
複製程式碼

首先,非公平鎖可以立即嘗試獲取鎖,如果失敗的話,會呼叫 AQS 中的 acquire 方法,其中 acquire 方法又會呼叫由自定義元件實現的 tryAcquire 方法:

protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}
複製程式碼

nonfairTryAcquire() 方法在 Sync 中已經預設實現:

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        // 使用 CAS 設定同步狀態
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // 整數溢位
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}
複製程式碼

這裡,首先會判斷的當前執行緒的狀態是否為 0,也就是該鎖是否處於空閒狀態,如果是的話則嘗試獲取鎖,設定成功將當前執行緒設定為持有鎖的執行緒。

否則的話,就判斷當前執行緒是否為持有鎖的執行緒,如果是的話,則增加同步狀態值,獲取到鎖,這裡也就驗證了鎖的可重入,再獲取了鎖之後,可以繼續獲取鎖,只需增加同步狀態值即可。

FairSync

FairSynclock() 方法的實現如下:

final void lock() {
    acquire(1);
}
複製程式碼

公平鎖只能呼叫 AQSacquire() 方法,再去呼叫由自定義元件實現的 tryAcquire() 方法:

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        // 是否有前驅節點
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}
複製程式碼

這裡唯一與非公平鎖不同的是在獲取同步狀態時,會呼叫 hasQueuedPredecessors 方法,這個方法用來判斷同步佇列中是否有前驅節點。也就是當前執行緒前面再沒有其他執行緒時,它才可以嘗試獲取鎖。

釋放鎖

ReentrantLockunlock 方法內部呼叫 AQSrelease 方法釋放鎖,而其中又呼叫了自定義元件實現的 tryRelease 方法:

protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    // 當前執行緒不是持有鎖的執行緒,不能釋放
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}
複製程式碼

首先,判斷當前執行緒是否是持有鎖的執行緒,如果不是會丟擲異常。如果是的話,再減去同步狀態值,判斷同步狀態是否為 0,即鎖被完全釋放,其他執行緒可以獲取同步狀態了。

如果沒有完全釋放,則僅使用 setState 方法設定同步狀態值。

指定公平性

ReentrantLock 的建構函式中可以指定公平性:

  • 預設建立一個非公平的鎖
public ReentrantLock() {
    sync = new NonfairSync();
}
複製程式碼
  • 建立一個指定公平性的鎖。
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}
複製程式碼

synchronized 和 ReentrantLock 區別

這裡總結一下 synchronized 和 ReentrantLock 的異同,它們之間的相同點如下:

  • 都可以用於實現執行緒間的同步訪問;
  • 兩者都是可重入鎖,即一個執行緒能夠對資源重複加鎖;

其不同點如下:

  • 同步實現機制不同:
    • synchronized 通過 Java 物件關聯的 Monitor 監視器實現(不考慮偏向鎖、輕量級鎖);
    • ReentrantLock 通過 CASAQSLockSupport 等共同實現;
  • 可見性實現機制不同:
    • synchronized 依賴 JVM 記憶體模型保證包含共享變數的多執行緒記憶體可見性。
    • ReentrantLock 通過 ASQvolatile 型別的 state 同步狀態值保證包含共享變數的多執行緒記憶體可見性。
  • 使用方式不同:
    • synchronized 可以用於修飾例項方法(鎖住例項物件)、靜態方法(鎖住類物件)、同步程式碼塊(指定的鎖物件)。
    • ReentrantLock 需要顯式地呼叫 lock 方法,並在 finally 塊中釋放。
  • 功能豐富程度不同:
    • synchronized 只提供最簡單的加鎖。
    • ReentrantLock 提供定時獲取鎖、可中斷獲取鎖、Condition(提供 awaitsignal 等方法)等特性。
  • 鎖型別不同:
    • synchronized 只支援非公平鎖。
    • ReentrantLock 提供公平鎖和非公平鎖實現。但非公平鎖相比於公平鎖效率較高。

synchronized 優化以前,它比較重量級,其效能比 ReentrantLock 要差很多,但是自從 synchronized 引入了偏向鎖、輕量級鎖(自旋鎖)、鎖消除、鎖粗化等技術後,兩者的效能就相差不多了。

一般來說,僅當需要使用 ReentrantLock 提供的其他特性時,例如:可中斷的、可定時的、可輪詢的、公平地獲取鎖等,才考慮使用 ReentrantLock。否則應該使用 synchronized,簡單方便。

相關文章