前言
ReentrantLock 提供了公平鎖和非公平鎖,只需要在構造方法中使用一個 boolean
引數即可。預設非公平鎖。
今天從原始碼層面看看區別和具體實現。
1. 類 UML 圖
ReentrantLock
內部有一個抽象類 Sync
,繼承了 AQS。
而公平鎖的實現就是 FairSync
,非公平鎖的實現就是 NodFairSync
。
兩把鎖的區別在於lock
方法的實現。
2. 公平鎖 lock 方法實現
final void lock() {
acquire(1);
}
複製程式碼
呼叫的是 AQS 的acquire
方法,熟悉 AQS 的同學都知道,AQS 會回撥子類的 tryAcquire
方法,看看公平鎖的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;
}
複製程式碼
說下邏輯:
- 獲取 state 變數,如果是 0,說明鎖可以獲取。
- 判斷 AQS 佇列中是否有等待的執行緒,如果沒有,就使用 CAS 嘗試獲取。獲取成功後,將 CLH 的持有執行緒修改為當前執行緒。
- 重入鎖邏輯。
- 如果失敗,返回 false, AQS 會將這個執行緒放進佇列,並掛起。
注意上面的第二步:判斷 AQS 佇列中是否有等待的執行緒。
這就是公平的體現。
再看看非公平鎖的區別。
3. 非公平鎖 lock 方法實現
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
複製程式碼
lock 方法就不同了,很衝動的一個方法,直接使用 CAS 獲取鎖,如果成功了,就設定鎖的持有執行緒為自己。很快速。所以預設使用了非公平鎖。
如果失敗了,就呼叫 AQS 的 acquire
方法。當然,我們看的還是tryAcquire
方法,在上面的程式碼中,tryAcquire
方法呼叫了父類Sync
的 nonfairTryAcquire
,為什麼在父類中呢?
在ReentrantLock
的 tryLock
方法中,也呼叫了該方法。因為這個方法是快速返回的。該方法不會讓等待時間久的執行緒獲取鎖。符合 tryLock
的設計。
方法實現如下:
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
複製程式碼
該方法相比較公平鎖的 tryAcquire
方法,少了一步判斷 AQS 佇列中是否有等待的執行緒的操作。
他要做的就是直接搶鎖,不讓給佇列裡那些等待時間長的。
搶不到再進入佇列。等待他的前置節點喚醒他。這個過程是公平的。
4. 總結
ReentrantLock
中的公平鎖和非公平鎖的區別就在於:呼叫 lock
方法獲取鎖的時候 要不要判斷 AQS 佇列中是否有等待的執行緒
,公平鎖為了讓每一個執行緒都均衡的使用鎖,就需要判斷,如果有,讓給他,非公平鎖很霸道,不讓不讓就不讓。
但如果失敗了,進入佇列了,進會按照 AQS 的邏輯來,整體順序就是公平的。
還有個注意的地方就是:ReentrantLock
的 tryLock(無超時機制)
方法使用的非公平策略。符合他的設計。
而 tryLock(long timeout, TimeUnit unit)
方法則會根據 Sync 的具體實現來呼叫。不會衝動的呼叫 nonfairTryAcquire
方法。