深入理解 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
,簡單方便。