JUC之JDK自帶鎖ReentrantLock
一、初識
ReentrantLock
出身自Java 1.5
,中文名可重入鎖
是Java JDK自帶獨佔鎖的唯一實現,也是最常用的鎖,是synchronized的升級版。
1. 我們中間有個synchronized
我們已經認識過synchronized了,知道她能幫我們實現執行緒同步提供原子性語義,同時又有可重入性。同時我們也已經知道了可重入性是什麼意思,也知道公平性的含義。
當然,我們很JRE如何實現synchronized,實現它的可重入性。
但我們可以通過閱讀ReentrantLock的原始碼,來加深對sychronized一些特性的理解。
2. 升級版,升了什麼
前面說了ReentrantLock是sychronized的升級版,那麼ReentrantLock升級了什麼,為我們帶哪些新特性呢?
- tryLock 嘗試獲取鎖,直接穿透(無視)
公平性
- isLocked 當前鎖是不是被持有(含自己和他人),用來監控系統(鎖)狀態
- hasQueuedThreas(has) 提供更多監控,這一系列含has方法都是實現狀態監控
- Fair And Non-Fair Model ReentrankLock提供公平與非公平兩個模式
- Condition 在Lock內用Condition代替Synchronized的Object監控
這些都是非常好用,非常實用的功能,而synchronized卻沒有的特徵。
還有還有,ReentrankLock還提供了一個可中斷的方法。
3. Condition
當把ReentrantLock當成synchronized時,你需要把Condition當成Object監控。功能和用法都一樣,只是名字不同而已。
專案 | 等待 | 喚醒 | 喚醒所有 | 用法 |
---|---|---|---|---|
Object | wait | notify | notifyAll | 在synchronized語塊內 |
Condition | await | signal | signalAll | 在Lock語塊內 |
二、你好!ReentrankLock
我們已經認識過公平性了,我們知道她的語義是是否先到先得
。那麼她怎麼實現的呢,我們好像還沒看過。接下來我們將通過閱讀ReentrantLock的原始碼,來看看她是怎麼實現公平
性的。先貼程式碼吧
1. 公平
// ReentranLock$FiarSync
final void lock() {
acquire(1);
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
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;
}
你應該從一小段程式碼裡看出三個東西,
1. 公平
2. 可重入
3. 獨佔式
JDK在實現鎖的時候通常用’0’和’大於0`表示鎖狀態,即沒鎖上和已上鎖。
從這一小段原始碼裡面可以看到
- ReentrantLock先自己嘗試去獲取鎖,即是檢查state的狀態碼
1. 對於state為0(即鎖沒被持有的時候),且前面沒人在排隊,理論上對這種情況特別簡單直接上鎖就可以了,如鎖操作是執行緒安全的。然後並沒有,我們之前在整理AQS框架
的時候已經瞭解了AQS是通過CAS實現同步
,即通過一組原子性的操作完成的。
當state不為0時,她就會去判斷是誰持有,如果是自己的話,依然可以再次獲得,這就是可重入性。同時state自增,此時可以反映兩個問題,state還代表遞迴層級,最多能支援
Integer.MAX_VALUE
層。- ReentrantLock在嘗試失敗之後,將進入等待佇列繼續等待,就是阻塞
2. 不公平
// ReentrantLock#NonFairSync
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) { // #1
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;
}
乍一看,好像沒啥差。
只不過一來就嘗試通過CAS修改條件來獲取獨佔鎖。其它,好像沒啥了。對了,還改了個不要臉的名字叫nonfairTryAcquire
。
細心的你應該還能看到#1
語句裡少了條件!hasQueuedPredecessors()
,即是不再管前面有沒有人在排隊,就伸手要去取鎖。正因為這兩個“不道德”的插隊操作完成了非公開性,從而獲到更高的OPS。
ReentrankLock預設實現就是非公平的,因為它能有更高的吞吐量。
三、再見AQS框架
在整理AQS框架
的時候,我們說AQS框架
提供一個基於FIFO等待佇列,可以用於構建鎖或者其他同步裝置的基礎框架。意在能夠成為實現大部分同步需求的基礎,她用法可以參考ReentrantLock的實現。
這裡再多說幾句,ReentrantLock是實現Lock介面、同時也實現Serializable。Serializable是說它的狀態可以被系列,而Lock提供Lock的相關語義和操作。到這裡為止,好像跟AQS沒什麼關係。對對對您說得對,沒錯沒錯是這樣。不過我們說Lock是通過AQS實現同步的。因此,ReentrantLock實際上是通過一個承繼AQS的內部類——同步器(Sync),實現同步,從而完全Lock功能的。
再看看ReentrantLock提供兩種Sync的實現完全公平性語義,即FairSync
和NonFairSync
兩個類。而Lock的功能完全Forwarding到Sync,由Sync具體實現。所以說AQS可以用來實現同步鎖。
四、CU! ReentrantLock
- ReentrantLock是一個獨佔鎖,可重入。
- ReentrantLock可實現公平和非公平,通過她的構造器的引數設定
- ReentrantLock是synchronized的升級版,可代替synchonized,更好用、更高效能