ReentrantLock
ReentrantLock
是一種可重入鎖,它指的是一個執行緒能夠對資源重複加鎖。ReentrantLock
與 synchronized
類似,能夠保證解決執行緒安全問題,但是卻提供了比 synchronized
更強大、靈活的機制,例如可中斷式的獲取鎖、可定時的獲取鎖等。
另外,ReentrantLock
也提供了公平鎖與非公平鎖的選擇,它們之間的區別主要就是看對鎖的獲取與獲取鎖的請求的順序是否是一致的,選擇公平鎖時,等待時間最長的執行緒會最優先獲取到鎖,但是公平鎖獲取的效率通常比非公平鎖要低。可以在構造方法中通過傳參的方式來具體指定選擇公平或非公平。
公平鎖
在 ReentrantLock
中,有一個抽象內部類 Sync
,它繼承自 AQS
,ReentrantLock
的大部分功能都委託給 Sync
進行實現,其內部定義了 lock()
抽象方法,預設實現了 nonfairTryAcquire()
方法,它是非公平鎖的預設實現。
Sync
有兩個子類:公平鎖 FairSync
和 NonFairSync
,實現了 Sync
中的 lock()
方法和 AQS
中的 tryAcquire()
方法。
NonFairSync
NonFairSync
中 lock()
方法的實現如下:
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
FairSync
中 lock()
方法的實現如下:
final void lock() {
acquire(1);
}
複製程式碼
公平鎖只能呼叫 AQS
的 acquire()
方法,再去呼叫由自定義元件實現的 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
方法,這個方法用來判斷同步佇列中是否有前驅節點。也就是當前執行緒前面再沒有其他執行緒時,它才可以嘗試獲取鎖。
釋放鎖
ReentrantLock
的 unlock
方法內部呼叫 AQS
的 release
方法釋放鎖,而其中又呼叫了自定義元件實現的 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
通過CAS
、AQS
和LockSupport
等共同實現;
- 可見性實現機制不同:
synchronized
依賴JVM
記憶體模型保證包含共享變數的多執行緒記憶體可見性。ReentrantLock
通過ASQ
中volatile
型別的state
同步狀態值保證包含共享變數的多執行緒記憶體可見性。
- 使用方式不同:
synchronized
可以用於修飾例項方法(鎖住例項物件)、靜態方法(鎖住類物件)、同步程式碼塊(指定的鎖物件)。ReentrantLock
需要顯式地呼叫lock
方法,並在finally
塊中釋放。
- 功能豐富程度不同:
synchronized
只提供最簡單的加鎖。ReentrantLock
提供定時獲取鎖、可中斷獲取鎖、Condition
(提供await
、signal
等方法)等特性。
- 鎖型別不同:
synchronized
只支援非公平鎖。ReentrantLock
提供公平鎖和非公平鎖實現。但非公平鎖相比於公平鎖效率較高。
在 synchronized
優化以前,它比較重量級,其效能比 ReentrantLock
要差很多,但是自從 synchronized
引入了偏向鎖、輕量級鎖(自旋鎖)、鎖消除、鎖粗化等技術後,兩者的效能就相差不多了。
一般來說,僅當需要使用 ReentrantLock
提供的其他特性時,例如:可中斷的、可定時的、可輪詢的、公平地獲取鎖等,才考慮使用 ReentrantLock
。否則應該使用 synchronized
,簡單方便。