ReentrantLock
是一種可重入鎖,它指的是一個線程可以對資源重複加鎖。ReentrantLock
與 synchronized
相似,可以保證解決線程安全問題,可是卻提供了比 synchronized
更強大、靈活的機制,例如可中斷式的獲取鎖、可定時的獲取鎖等。java
另外,ReentrantLock
也提供了公平鎖與非公平鎖的選擇,它們之間的區別主要就是看對鎖的獲取與獲取鎖的請求的順序是不是一致的,選擇公平鎖時,等待時間最長的線程會最優先獲取到鎖,可是公平鎖獲取的效率一般比非公平鎖要低。能夠在構造方法中經過傳參的方式來具體指定選擇公平或非公平。安全
在 ReentrantLock
中,有一個抽象內部類 Sync
,它繼承自 AQS
,ReentrantLock
的大部分功能都委託給 Sync
進行實現,其內部定義了 lock()
抽象方法,默認實現了 nonfairTryAcquire()
方法,它是非公平鎖的默認實現。多線程
Sync
有兩個子類:公平鎖 FairSync
和 NonFairSync
,實現了 Sync
中的 lock()
方法和 AQS
中的 tryAcquire()
方法。函數
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
中已經默認實現:ui
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
,也就是該鎖是否處於空閒狀態,若是是的話則嘗試獲取鎖,設置成功將當前線程設置爲持有鎖的線程。spa
不然的話,就判斷當前線程是否爲持有鎖的線程,若是是的話,則增長同步狀態值,獲取到鎖,這裏也就驗證了鎖的可重入,再獲取了鎖以後,能夠繼續獲取鎖,只需增長同步狀態值便可。線程
FairSync
中 lock()
方法的實現以下:code
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
經過 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
,簡單方便。