## 1 更高級的鎖—深入解析Lock
> 沒有智慧的頭腦,就象沒有臘燭的燈籠。
> ——列夫·托爾斯泰
前面的章節我們剛剛學習了 Java 的內置鎖,也就是 synchronized 關鍵字的使用。在 Java 5.0 之前只有 synchronized 和 volatile 可以用來進行同步。在 Java 5.0 之后,出現了新的同步機制,也就是使用 ReentrantLock 顯式的加鎖。而 ReentrantLock 的誕生并不是用來取代 synchroinized。而是應該在 synchroinized 無法滿足我們需求的時候才使用 ReentrantLock。
我們生活中也是一樣的,不要過分追求名牌、追求功能齊全。其實絕大多數情況下,我們選擇一般的產品已經足夠用了。一個產品 80% 的功能其實在你淘汰它之前都不會用到。當然,如果你確實有需求,那么還是應該選擇更為高級的產品。
## 1、ReentrantLock 的使用
**簡單應用**
ReentrantLock 的使用相比較 synchronized 會稍微繁瑣一點,所謂顯示鎖,也就是你在代碼中需要主動的去進行 lock 操作。一般來講我們可以按照下面的方式使用 ReentrantLock。
~~~java
Lock lock = new ReentrantLock();
lock.lock();
try {
doSomething();
}finally {
lock.unlock();
}
~~~
lock.lock () 就是在顯式的上鎖。上鎖后,下面的代碼塊一定要放到 try 中,并且要結合 finally 代碼塊調用 lock.unlock () 來釋放鎖,否則一定 doSomething 方法中出現任何異常,這個鎖將永遠不會被釋放掉。
**公平鎖和非公平鎖**
synchronized 是非公平鎖,也就是說每當鎖匙放的時候,所有等待鎖的線程并不會按照排隊順去依次獲得鎖,而是會再次去爭搶鎖。ReentrantLock 相比較而言更為靈活,它能夠支持公平和非公平鎖兩種形式。只需要在聲明的時候傳入 true。
~~~java
Lock lock = new ReentrantLock(true);
~~~
而默認的無參構造方法則會創建非公平鎖。
**tryLock**
前面我們通過 lock.lock (); 來完成加鎖,此時加鎖操作是阻塞的,直到獲取鎖才會繼續向下進行。ReentrantLock 其實還有更為靈活的枷鎖方式 tryLock。tryLock 方法有兩個重載,第一個是無參數的 tryLock 方法,被調用后,該方法會立即返回獲取鎖的情況。獲取為 true,未能獲取為 false。我們的代碼中可以通過返回的結果進行進一步的處理。第二個是有參數的 tryLock 方法,通過傳入時間和單位,來控制等待獲取鎖的時長。如果超過時間未能獲取鎖則放回 false,反之返回 true。使用方法如下:
~~~java
if(lock.tryLock(2, TimeUnit.SECONDS)){
try {
doSomething();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}else{
doSomethingElse();
}
~~~
我們如果不希望無法獲取鎖時一直等待,而是希望能夠去做一些其它事情時,可以選擇此方式。
## 2、lock 方法源碼分析
我們先從 lock 方法看起。lock 方法的代碼如下:
~~~java
public void lock() {
sync.lock();
}
~~~
通過內置的 sync 對象加鎖,那么 sync 對象是什么呢?我們來看 ReentrantLock 的無參構造函數:
~~~java
public ReentrantLock() {
sync = new NonfairSync();
}
~~~
有參的構造函數:
~~~java
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
~~~
FairSync 和 NonFairSync 都繼承自 Sync。它們的繼承關系如下圖:

都是最終繼承自 AbstractQueuedSynchronizer。這就是 Java 中著名的 AQS。通過查看 AQS 的注釋我們了解到, AQS 依賴先進先出隊列實現了阻塞鎖和相關的同步器(信號量、事件等)。AQS 內部有一個 volatile 類型的 state 屬性,實際上多線程對鎖的競爭體現在對 state 值寫入的競爭。一旦 state 從 0 變為 1,代表有線程已經競爭到鎖,那么其它線程則進入等待隊列。等待隊列是一個鏈表結構的 FIFO 隊列,這能夠確保公平鎖的實現。同一線程多次獲取鎖時,如果之前該線程已經持有鎖,那么對 state 再次加 1。釋放鎖時,則會對 state-1。直到減為 0,才意味著此線程真正釋放了鎖。

我們回過頭來,繼續跟進 sync.lock (); 的源代碼。我們對代碼的分析選擇公平鎖這條線。FairSync 實現的 lock 代碼很簡單:
~~~java
final void lock() {
acquire(1);
}
~~~
在 FairSync 并沒有重寫 acquire 方法代碼。調用的為 AbstractQueuedSynchronizer 的代碼,如下:
~~~java
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
~~~
首先調用一次 tryAcquire 方法。如果 tryAcquire 方法返回 true,那么 acquire 就會立即返回。但如果 tryAcquire 返回了 false,那么則會先調用 addWaiter,把當前線程包裝成一個等待的 node,加入到等待隊列。然后調用 acquireQueued 嘗試排隊獲取鎖,如果成功后發現自己被中斷過,那么返回 true,導致 selfInterrupt 被觸發,這個方里只是調用 Thread.currentThread ().interrupt (); 進行 interrupt。
acquireQueued 代碼如下:
~~~java
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
~~~
在此方法中進入自旋,不斷查看自己排隊的情況。如果輪到自己( header 是已經獲取鎖的線程,而 header 后面的線程是排隊到要去獲取鎖的線程),那么調用 tryAcquire 方法去獲取鎖,然后把自己設置為隊列的 header。在自旋中,如果沒有排隊到自己,還會檢查是否應該應該被中斷。
整個獲取鎖的過程我們可以總結下:
1. 直接通過 tryAcquire 嘗試獲取鎖,成功直接返回;
2. 如果沒能獲取成功,那么把自己加入等待隊列;
3. 自旋查看自己的排隊情況;
4. 如果排隊輪到自己,那么嘗試通過 tryAcquire 獲取鎖;
5. 如果沒輪到自己,那么回到第三步查看自己的排隊情況。
從以上過程我們可以看到鎖的獲取是通過 tryAcquire 方法。而這個方法在 FairSync 和 NonfairSync 有不同實現,我們來分析在 FairSync 中的實現。
~~~java
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;
}
~~~
實際上它的實現和 NonfairSync 的實現,值是在 c==0 時,多了對 hasQueuedPredecessors 方法的調用。故名思義,這個方法做的事情就是判斷當前線程是否前面還有排隊的線程。當它前面沒有排隊線程,說明已經排隊到自己了,這是才會通過 CAS 的的方式去改變 state 值為 1,如果成功,那么說明當前線程獲取鎖成功。接下來就是調用 setExclusiveOwnerThread 把自己設置成為鎖的擁有者。else if 中邏輯則是在處理重入邏輯,如果當前線程就是鎖的擁有者,那么會把 state 加 1 更新回去。
通過以上分析,我們可以看出 AbstractQueuedSynchronizer 提供 acquire 方法的模板邏輯,但其中真正對鎖的獲取方法 tryAcquire,是在不同子類中實現的,這是很好的設計思想。
## 3、unlock 方法源碼分析
下面我們來分析 unlock 的源碼:
~~~java
public void unlock() {
sync.release(1);
}
~~~
和 lock 很像,實際調用的是 sync 實現類的 release 方法。和 lock 方法一樣,這個 release 方法在 AbstractQueuedSynchronizer 中,
~~~java
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
~~~
這個方法中會先執行 tryRelease,它的實現也在 AbstractQueuedSynchronizer 的子類 Sync 中,如果釋放鎖成功,那么則會通過 unparkSuccessor 方法找到隊列中第一個 waitStatus<0 的線程進行喚醒。我們下面看一下 tryRelease 方法代碼:
~~~java
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;
}
~~~
還是比較簡單,釋放的時候會把 state 減 1,如果減到 0,那么說明沒有線程持有鎖,則會設置 free=true 并且清空鎖的持有者。如果 state 值還是大于 0,這說明可重入鎖還有其它線程持有,那么鎖并沒有被真正釋放,僅僅是減少了持有的數量,所以返回 false。
## 總結
本節學習了 ReentrantLock 的使用及其核心源代碼,其實 Lock 相關的代碼還有很多。我們可以嘗試自己去閱讀。ReentrantLock 的設計思想是通過 FIFO 的隊列保存等待鎖的線程。通過 volatile 類型的 state 保存鎖的持有數量,從而實現了鎖的可重入性。而公平鎖則是通過判斷自己是否排隊成功,來決定是否去爭搶鎖。學習完本節相信你一定會有疑問,為什么在內置鎖之外又設計了 Lock 顯式鎖呢?下一節,我們將對這兩種鎖進行對比,看看各自適合的場景。
- 前言
- 第1章 Java并發簡介
- 01 開篇詞:多線程為什么是你必需要掌握的知識
- 02 絕對不僅僅是為了面試—我們為什么需要學習多線程
- 03 多線程開發如此簡單—Java中如何編寫多線程程序
- 04 人多力量未必大—并發可能會遇到的問題
- 第2章 Java中如何編寫多線程
- 05 看若兄弟,實如父子—Thread和Runnable詳解
- 06 線程什么時候開始真正執行?—線程的狀態詳解
- 07 深入Thread類—線程API精講
- 08 集體協作,什么最重要?溝通!—線程的等待和通知
- 09 使用多線程實現分工、解耦、緩沖—生產者、消費者實戰
- 第3章 并發的問題和原因詳解
- 10 有福同享,有難同當—原子性
- 11 眼見不實—可見性
- 12 什么?還有這種操作!—有序性
- 13 問題的根源—Java內存模型簡介
- 14 僵持不下—死鎖詳解
- 第4章 如何解決并發問題
- 15 原子性輕量級實現—深入理解Atomic與CAS
- 16 讓你眼見為實—volatile詳解
- 17 資源有限,請排隊等候—Synchronized使用、原理及缺陷
- 18 線程作用域內共享變量—深入解析ThreadLocal
- 第5章 線程池
- 19 自己動手豐衣足食—簡單線程池實現
- 20 其實不用造輪子—Executor框架詳解
- 第6章 主要并發工具類
- 21 更高級的鎖—深入解析Lock
- 22 到底哪把鎖更適合你?—synchronized與ReentrantLock對比
- 23 按需上鎖—ReadWriteLock詳解
- 24 經典并發容器,多線程面試必備—深入解析ConcurrentHashMap上
- 25 經典并發容器,多線程面試必備—深入解析ConcurrentHashMap下
- 26不讓我進門,我就在門口一直等!—BlockingQueue和ArrayBlockingQueue
- 27 倒數計時開始,三、二、一—CountDownLatch詳解
- 28 人齊了,一起行動—CyclicBarrier詳解
- 29 一手交錢,一手交貨—Exchanger詳解
- 30 限量供應,不好意思您來晚了—Semaphore詳解
- 第7章 高級并發工具類及并發設計模式
- 31 憑票取餐—Future模式詳解
- 32 請按到場順序發言—Completion Service詳解
- 33 分階段執行你的任務-學習使用Phaser運行多階段任務
- 34 誰都不能偷懶-通過 CompletableFuture 組裝你的異步計算單元
- 35 拆分你的任務—學習使用Fork/Join框架
- 36 為多線程們安排一位經理—Master/Slave模式詳解
- 第8章 總結
- 37 結束語