學(xué)習(xí) AQS 之前, 需要對以下幾點(diǎn)內(nèi)容都有所了解. 本章內(nèi)容將先從以下幾點(diǎn)開始然后逐步到 AQS.
- CAS 概念 (在前面幾篇)
- LockSupport 概念
- CLH 隊(duì)列鎖概念
- AQS 概念
- 從 ReentrantLock 重入鎖來看 AQS
一. CAS
關(guān)于 CAS 在前面文章有寫過, 傳送門 java 基礎(chǔ)回顧 - 基于 CAS 實(shí)現(xiàn)原子操作的基本理解
?
二. LockSupport
2.1 傳統(tǒng)線程等待/喚醒機(jī)制
日常我們經(jīng)常使用的等待與喚醒機(jī)制無非就是下面兩種方式
-
Object
類中的wait
和notify
方法實(shí)現(xiàn)線程等待和喚醒. -
Condition
接口中的await
和signal
方法實(shí)現(xiàn)線程的等待和喚醒.
這兩種方式如果大家都使用過的話, 就會(huì)知道會(huì)有以下兩個(gè)問題. 也就是傳統(tǒng)的 synchronized
和 Lock
實(shí)現(xiàn)等待喚醒通知的約束
- 線程先要獲得并持有鎖, 必須在鎖塊
(synchronized 或 lock)
中. - 必須要先等待后喚醒, 線程才能夠被喚醒
而 LockSupport
就解決了傳統(tǒng)的等待/喚醒機(jī)制的痛點(diǎn), 下面一起來看下 LockSupport
究竟是什么.
2.2 LockSupport 是什么
LockSupport
是一個(gè)線程阻塞工具類, 內(nèi)部所有方法都是靜態(tài)的, 可以讓線程在任意位置阻塞, 阻塞之后也有對應(yīng)的喚醒方法. 可以喚醒任意指定線程.
LockSupport
使用了一種名為 permit(許可)
的概念來做到阻塞和喚醒線程的功能, 每個(gè)線程都有一個(gè) permit(許可)
.
permit(許可)
只有兩個(gè)值: 0 與 1. 默認(rèn)為 0.
?
2.3 LockSupport 的阻塞方法
public static void park() {
UNSAFE.park(false, 0L);
}
permit(許可)
默認(rèn)是 0, 所以一開始調(diào)用阻塞方法 park()
, 當(dāng)前線程就會(huì)阻塞, 直到別的線程將當(dāng)前線程的 permit(許可)
設(shè)置為 1 時(shí)才會(huì)被喚醒, 被喚醒后會(huì)將 permit(許可)
再次設(shè)置為 0 并返回. 別的線程調(diào)用了 interrupt
中斷該線程, 也會(huì)立即返回.
?
2.4 LockSupport 的喚醒方法
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
在調(diào)用 unpark(thread)
方法后, 就會(huì)將傳入 thread
線程的 permit(許可)
設(shè)置為 1, 并自動(dòng)喚醒傳入的 thread
線程, 即之前阻塞中的 LockSupport.park()
方法會(huì)立即返回. 多次調(diào)用 unpark()
方法并不會(huì)累加, 值還會(huì)是 1.
?
2.5 LockSupport 簡單例子
這是一個(gè)典型的先阻塞后喚醒的例子:
thread1
進(jìn)入后調(diào)用 LockSupport.park()
發(fā)現(xiàn) permit(許可)
值為 0, 直接進(jìn)入阻塞狀態(tài). 3 秒后進(jìn)入到 thread2
調(diào)用 LockSupport.unpark(thread1)
將 thread1
中的 permit(許可)
值改為 1, 并喚醒 thread1
. 最后在 thread1
中 LockSupport.park()
就會(huì)立刻返回, 結(jié)束阻塞狀態(tài)同時(shí)將 permit(許可)
值再改為 0.
如果讓 thread2
先給 thread1
發(fā)放許可, 然后 thread1
再進(jìn)行阻塞. 又會(huì)輸出什么呢.
輸出結(jié)果是沒問題的, 不會(huì)被阻塞. 這也就解決了傳統(tǒng)的等待/喚醒機(jī)制的痛點(diǎn).
?
2.6 LockSupport 總結(jié)
-
LockSupport
和每個(gè)使用它的線程都有一個(gè)(permit)許可
關(guān)聯(lián).permit
相當(dāng)于1,0的開關(guān), 默認(rèn)是0. - 如果將
(permit)許可
看成憑證可能會(huì)更容易理解一點(diǎn). 線程阻塞需要消耗憑證(permit)
, 這個(gè)憑證最多只有1個(gè). 默認(rèn)是沒有憑證的.也就是 0. - 調(diào)用
park
方法時(shí)候, 會(huì)先判斷有沒有憑證, 也就是permit
值是否為 1.- 如果有憑證, 則會(huì)直接消耗掉這個(gè)憑證然后正常退出. 也就是將
permit
的值改為 0, 然后直接返回.不會(huì)阻塞. 這就是為什么LockSupport
可以先喚醒后阻塞的原理. - 如果沒有憑證, 就必須阻塞等待有憑證可用. 也就是等待
permit
的值由 0 變?yōu)?1. 阻塞后, 一旦在外部為此線程發(fā)放了憑證(外部調(diào)用了unpark
方法, 將permit
改為 1), 那么將結(jié)束阻塞狀態(tài), 并消費(fèi)掉這個(gè)憑證.(將permit
改為 0).
- 如果有憑證, 則會(huì)直接消耗掉這個(gè)憑證然后正常退出. 也就是將
- 調(diào)用
unpark
方法, 會(huì)為指定線程發(fā)放一個(gè)憑證, 將指定線程的permit
值改 為 1. 但憑證最多只能有1個(gè), 累加無效.
問題: 如果先喚醒兩次再阻塞兩次, 最終結(jié)果是什么呢? 答案是最終還是會(huì)阻塞線程, 因?yàn)閼{證的數(shù)量最多就是 1, 喚醒再多次也沒用, 但是只要阻塞一次, 就會(huì)消費(fèi)掉這個(gè)憑證, 再進(jìn)行阻塞, 就沒有憑證了, 所以還會(huì)阻塞.
如果有興趣研究 LockSupport
源碼的同學(xué)可以看下這篇文章, 【細(xì)談Java并發(fā)】談?wù)凩ockSupport.
?
三. CLH 隊(duì)列鎖的概念
CLH 名字的由來即 Craig, Landin, Hagersten 國外三個(gè)大牛名字的縮寫.
CLH 隊(duì)列是單向隊(duì)列, 其主要特點(diǎn)是自旋檢查前驅(qū)節(jié)點(diǎn)的 locked
狀態(tài)
CLH 隊(duì)列鎖一個(gè)自旋鎖. 能確保無饑餓性. 提供先來先服務(wù)的公平性.
CLH 隊(duì)列鎖也是一種基于鏈表的可擴(kuò)展, 高性能, 公平的自旋鎖. 申請鎖線程僅在本地變量上自旋, 它不斷輪詢前面一個(gè)節(jié)點(diǎn)的狀態(tài), 假設(shè)發(fā)現(xiàn)上一個(gè)節(jié)點(diǎn)釋放了鎖就結(jié)束自旋.
3.1 CLH 隊(duì)列鎖的獲取
CLH 隊(duì)列中當(dāng)一個(gè)線程需要獲取鎖的時(shí)候, 會(huì)將其封裝成為一個(gè) QNode
節(jié)點(diǎn), 將其中的 locked
設(shè)置為 true
, 表示需要獲取鎖, myPred
則表示對其前驅(qū)節(jié)點(diǎn)的引用.
?
線程 A 要獲取鎖的時(shí)候, 會(huì)先使自己成為隊(duì)列的尾部, 同時(shí)獲取一個(gè)指向其前驅(qū)節(jié)點(diǎn)的引用
myPred
線程 B 想要獲取鎖, 同樣需要放到隊(duì)列的尾部, 并將
myPred
指向線程 A 的節(jié)點(diǎn).?
每個(gè)線程就在前驅(qū)節(jié)點(diǎn)的
locked
字段上自旋, 直到前驅(qū)節(jié)點(diǎn)釋放鎖. 也就是前驅(qū)節(jié)點(diǎn)的 locked
字段變?yōu)?false
?
當(dāng)一個(gè)線程需要釋放鎖的時(shí)候, 將當(dāng)前節(jié)點(diǎn)的
locked
設(shè)置為 false
, 同時(shí)回收前驅(qū)節(jié)點(diǎn). 如下圖所示. 前驅(qū)節(jié)點(diǎn)釋放鎖, 線程 A 的 myPred
所指向前驅(qū)節(jié)點(diǎn)的 locked
字段變?yōu)?false
, 線程 A 就可以獲取到鎖.?
四. AQS 概念
4.1 AQS 簡介
AQS 即 AbstractQueuedSynchronizer
抽象的隊(duì)列式同步器. 是用來構(gòu)建鎖或者其他同步組件的基礎(chǔ)框架. 它不能被實(shí)例化, 設(shè)計(jì)之初就是為了讓子類通過繼承 AQS 并實(shí)現(xiàn)它的抽象方法來管理同步狀態(tài).
簡單理解就是: 如果被請求的共享資源空閑, 則將當(dāng)前請求資源的線程設(shè)置為有效的工作線程, 并將共享資源設(shè)置為鎖定狀態(tài), 如果被請求的共享資源被占用, 那么就需要一套線程阻塞等待以及喚醒時(shí)鎖分配的機(jī)制, 這個(gè)機(jī)制就是 AQS, 是基于 CLH 隊(duì)列的變體實(shí)現(xiàn)的. 它將每一條請求共享資源的線程封裝成隊(duì)列的一個(gè)節(jié)點(diǎn) Node
, 將暫時(shí)獲取不到鎖的節(jié)點(diǎn)Node
加入到隊(duì)列中. 通過 CAS, 自旋, 以及 LockSupport.park()
的方式維護(hù)內(nèi)部的一個(gè)使用 volatile
修飾的 int
類型共享變量 state
的狀態(tài). 使并發(fā)達(dá)到同步的效果.
state
變量可以理解為共享資源, state
的訪問方式有以下三種, 根據(jù)修飾符protected final
, 說明它們是不可以被子類重寫的, 但是可以在子類中進(jìn)行調(diào)用, 這也就意味這之類可以根據(jù)自己的邏輯來決定如何使用 state
的值.
-
getState()
: 獲取當(dāng)前同步狀態(tài) -
setState(int newState)
: 設(shè)置當(dāng)前同步狀態(tài) -
compareAndSetState(int expect, int update)
: 使用 CAS 設(shè)置當(dāng)前狀態(tài), 該方法可以保證狀態(tài)設(shè)置的原子性.
ReentrantLock | CountDownLatch | ReentrantReadWriteLock | Semaphore
這些都是基于 AQS 實(shí)現(xiàn)的. AQS 的子類應(yīng)被定義為內(nèi)部類, 作為內(nèi)部的helper
對象. 如ReentrantLock
, 它便是通過內(nèi)部的Sync
對象來繼承 AQS 的.
CLH 是單向隊(duì)列, 這里說的基于 CLH 的變體是基于鏈表實(shí)現(xiàn)的雙向同步隊(duì)列, 在 CLH 的基礎(chǔ)上進(jìn)行了變種. CLH 的特點(diǎn)是自旋檢查前驅(qū)節(jié)點(diǎn)的
locked
狀態(tài). 而 AQS 同步隊(duì)列是雙向隊(duì)列, 每個(gè)節(jié)點(diǎn)也有狀態(tài)waitStatus
, 而其也不是一直對前驅(qū)節(jié)點(diǎn)的狀態(tài)自旋, 而是自旋一段時(shí)間后阻塞讓出 CPU 時(shí)間片, 等待前驅(qū)節(jié)點(diǎn)主動(dòng)喚醒后繼節(jié)點(diǎn).
state = 0,代表沒有線程持有鎖,state > 0 有線程持有鎖,并且 state 的大小是重入的次數(shù)
4.2 AQS 同步器與鎖之間的關(guān)系
AQS 同步器是實(shí)現(xiàn)鎖或者任意同步組件的關(guān)鍵, 在鎖的實(shí)現(xiàn)中聚合同步器. 可以這樣理解二者之間的關(guān)系.
- 鎖是面向使用者的, 它定義了使用者與鎖交互的接口, 隱藏了實(shí)現(xiàn)的細(xì)節(jié).
- 同步器是面向鎖的實(shí)現(xiàn)者, 它簡化了鎖的實(shí)現(xiàn)方式, 平布了同步狀態(tài)的管理, 線程的排隊(duì), 等待與喚醒等底層操作.
?
4.3 資源的獲取與釋放
在 AQS 中定義了兩種資源的共享方式.
- 獨(dú)占式
Exclusive
: 只有一個(gè)線程能執(zhí)行, 例如ReentrantLock
- 共享式
Share
: 多個(gè)線程可以同時(shí)執(zhí)行, 如Semaphore, CountDownLatch, ReadWriteLock
.
不同的自定義同步器爭用共享資源的方式也不同, 自定義同步器在實(shí)現(xiàn)時(shí)只需要實(shí)現(xiàn)共享資源 state
的獲取與釋放即可, 至于具體等待隊(duì)列的維護(hù), AQS 已經(jīng)在頂層實(shí)現(xiàn)好了. 自定義同步器時(shí)主要實(shí)現(xiàn)以下幾個(gè)方法.
-
isHeldExclusively()
: 當(dāng)前線程是否正在獨(dú)占資源. -
tryAcquire(int arg)
: 獨(dú)占式. 嘗試獲取資源. 成功返回true
, 失敗返回false
. -
tryRelease(int)
: 獨(dú)占式. 嘗試釋放資源. 成功返回true
, 失敗返回false
. -
tryAcquireShared(int)
: 共享式. 嘗試獲取資源. 返回大于等于 0 的值表示成功, 反之獲取失敗. -
tryReleaseShared(int)
: 共享式. 嘗試釋放資源. 如果釋放后允許喚醒后續(xù)等待節(jié)點(diǎn)返回true
, 否則返回false
.
以 ReentrantLock
為例. state
的初始值為 0, 表示未鎖定狀態(tài). 當(dāng) A 線程 Lock()
時(shí), 會(huì)調(diào)用 tryAcquire(int arg)
方法獨(dú)占該鎖并將 state + 1
. 后面其他線程再調(diào)用 tryAcquire(int arg)
時(shí)就會(huì)失敗, 直到 A 線程調(diào)用了 unLock()
后將 state = 0
釋放鎖為止, 其他線程才會(huì)有機(jī)會(huì)獲取到鎖. A 線程在釋放之前, 是可以自己重復(fù)獲取次鎖的, 即將 state
累加. 最后釋放的時(shí)候, 重入幾次鎖就需要釋放幾次鎖, 這樣才能保證 state
的值變?yōu)?0. 這便是鎖的可重入概念.
一般來說, 我們自定義同步器時(shí), 要么是獨(dú)占的方式, 要么是共享的方式. 但是 AQS 也支持同時(shí)實(shí)現(xiàn)獨(dú)占和共享兩種方式. 例如 ReentrantReadWriteLock
?
4.4 AQS 中的 Node 節(jié)點(diǎn)
上面說過了, 在 AQS 中將請求共享資源的線程都封裝成為了一個(gè) Node
節(jié)點(diǎn). 現(xiàn)在我們來看一下這個(gè) Node
里面都包含了什么.
AbstractQueuedSynchronizer.java
380 行
static final class Node {
//表示線程以共享的模式等待鎖
static final Node SHARED = new Node();
//表示線程以獨(dú)占的模式等待鎖
static final Node EXCLUSIVE = null;
//節(jié)點(diǎn)狀態(tài)值----表示線程獲取鎖的請求已取消. 當(dāng)timeout或被中斷(響應(yīng)中斷的情況下),會(huì)觸發(fā)變更為此狀態(tài),進(jìn)入該狀態(tài)后的節(jié)點(diǎn)將不會(huì)再變化
static final int CANCELLED = 1;
//節(jié)點(diǎn)狀態(tài)值----表示后繼節(jié)點(diǎn)在等待當(dāng)前節(jié)點(diǎn)喚醒. 后繼節(jié)點(diǎn)入隊(duì)時(shí), 會(huì)將前繼節(jié)點(diǎn)的狀態(tài)更新為此狀態(tài).
static final int SIGNAL = -1;
//節(jié)點(diǎn)狀態(tài)值----表示線程正在等待狀態(tài) 這個(gè)狀態(tài)只在condition await時(shí)設(shè)置
static final int CONDITION = -2;
//節(jié)點(diǎn)狀態(tài)值----表示共享模式下, 前繼節(jié)點(diǎn)不僅會(huì)喚醒其后繼節(jié)點(diǎn), 同事也可能喚醒后繼節(jié)點(diǎn)的后繼節(jié)點(diǎn).
static final int PROPAGATE = -3;
//節(jié)點(diǎn)狀態(tài), 節(jié)點(diǎn)在獲取鎖和釋放鎖的狀態(tài). 上面4 個(gè)節(jié)點(diǎn)狀態(tài)對應(yīng)此變量.
volatile int waitStatus;
//前驅(qū)指針
volatile Node prev;
//后繼指針
volatile Node next;
//記錄阻塞的線程
volatile Thread thread;
//condition 中是記錄下一個(gè)節(jié)點(diǎn),
//Lock 中是記錄當(dāng)前的 node 是獨(dú)占 node 還是共享 node
Node nextWaiter;
//如果當(dāng)前節(jié)點(diǎn)是以共享模式等待,則返回 true.
final boolean isShared() {
return nextWaiter == SHARED;
}
//返回前驅(qū)節(jié)點(diǎn)
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
//構(gòu)造函數(shù)1 不存放任何線程, 用于生成哨兵節(jié)點(diǎn)
Node() { // Used to establish initial head or SHARED marker
}
//構(gòu)造函數(shù) 2 用于鎖
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
//構(gòu)造函數(shù) 3 用于 Condition
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}
這里需要說明一下, AQS 的同步隊(duì)列是有些特別的, 其 head
節(jié)點(diǎn)是一個(gè)空節(jié)點(diǎn)也可稱為哨兵節(jié)點(diǎn), 沒有記錄線程 node.thread = null
, 其后繼節(jié)點(diǎn)才是實(shí)質(zhì)性的有線程的節(jié)點(diǎn), 這樣做的好處是. 當(dāng)最后一個(gè)有線程的節(jié)點(diǎn)出隊(duì)后, 不需要想著清空隊(duì)列, 同時(shí)下次有新節(jié)點(diǎn)入隊(duì)也不需要重新實(shí)例化隊(duì)列. 所以隊(duì)列為空時(shí), head = tail = null
, 當(dāng)?shù)谝粋€(gè)線程節(jié)點(diǎn)入隊(duì)時(shí), 會(huì)先初始化, head, tail
先指向一個(gè)空節(jié)點(diǎn). 再將新節(jié)點(diǎn)作為當(dāng)前 tail
的下一個(gè)節(jié)點(diǎn).通過 CAS 設(shè)置成功后, 將新節(jié)點(diǎn)設(shè)置為新的 tail
節(jié)點(diǎn)即可. 入隊(duì),出隊(duì)操作后面分析 ReentrantLock
的時(shí)候會(huì)分析到. 這里不再進(jìn)行說明.
弄明白了原理及節(jié)點(diǎn)這些后, 下面我們從 ReentrantLock
來解讀 AQS.
?
五. 從 ReentrantLock 來看 AQS
5.1 ReentrantLock 結(jié)構(gòu)
ReentrantLock
是可重入鎖. 重入鎖的概念在上面 4.3 資源的獲取與釋放中已經(jīng)解釋過. 忘記的可以翻看一下.
上面說過 AQS 是為了讓子類通過繼承 AQS 并實(shí)現(xiàn)它的抽象方法來管理同步狀態(tài)的. AQS 的子類應(yīng)當(dāng)被定義為內(nèi)部類, 作為內(nèi)部的 helper
對象. 那我們先看 ReentrantLock
的結(jié)構(gòu)是否是這樣的.
public class ReentrantLock implements Lock, java.io.Serializable {
...
abstract static class Sync extends AbstractQueuedSynchronizer {
abstract void lock();
...
}
static final class FairSync extends Sync {
...
}
static final class NonfairSync extends Sync {
...
}
...
}
可以看到 ReentrantLock
內(nèi)部的 Sync
對象繼承了 AQS, 但是 Sync
又是一個(gè)靜態(tài)抽象類, FairSync
與 NonfairSync
又都繼承了 Sync
. 這兩個(gè)方法類分別是 ReentrantLock
內(nèi)部實(shí)現(xiàn)的 公平鎖與非公平鎖. 他們分別都實(shí)現(xiàn)了 Sync
內(nèi)部的 lock
方法. 那么這兩個(gè)對象又是什么時(shí)候創(chuàng)建的呢. 現(xiàn)在跳轉(zhuǎn)到 ReentrantLock
的構(gòu)造函數(shù).
在 ReentrantLock
的有兩個(gè)構(gòu)造函數(shù), 分別如下.
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
我們平時(shí)使用的基本都是非公平鎖. 也就是無參的構(gòu)造函數(shù). 通過調(diào)用構(gòu)造函數(shù)及傳入?yún)?shù)的不同, 創(chuàng)建的鎖也不相同. 我們來分析非公平鎖也就是 NonfairSync
的獲取鎖與釋放鎖的流程. 其實(shí)他們兩個(gè)差不多, 無非就是多了一個(gè)判斷條件, 這點(diǎn)在最后會(huì)單獨(dú)來分析一下.
非公平鎖: 不管是否有等待隊(duì)列, 如果可以獲取鎖, 則立刻占有鎖.
公平鎖: 講究先來先到. 線程在獲取鎖時(shí), 如果這個(gè)鎖的等待隊(duì)列中已經(jīng)有線程在等待, 那么當(dāng)前線程就會(huì)進(jìn)入等待隊(duì)列中.
?
5.2 從 ReentrantLock.lock() 開始
假如現(xiàn)在有 A, B 兩個(gè)線程來獲取鎖. 并且我們創(chuàng)建的是非公平鎖, 也就是通過無參構(gòu)造創(chuàng)建的 ReentrantLock
, 那么調(diào)用的 lock
方法, 其實(shí)是調(diào)用了 NonfairSync
的 lock
. 方法.
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
final void lock() {
//通過 CAS 改變 AQS 中 state 的值. 期望是 0, 改為 1, 成功返回 true.
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
...
}
線程A 進(jìn)來執(zhí)行 if
內(nèi)語句, 線程 B 進(jìn)來執(zhí)行 else
語句.
- A 線程執(zhí)行到
lock()
內(nèi)部的compareAndSetState(0,1)
的時(shí)候, AQS 中state
值默認(rèn)是 0, 所以這里成立, 返回了true
, 然后調(diào)用setExclusiveOwnerThread
方法記錄下獨(dú)占模式下鎖持有者的線程. - 當(dāng) B 線程執(zhí)行
if
內(nèi)的語句的時(shí)候, 返回的就是false
了, 因?yàn)槠谕挡粚? 就調(diào)用了 AQS 的acquire(1)
.
?
5.3 AbstractQueuedSynchronizer.acquire
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {
...
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
...
}
這里又調(diào)用了 tryAcquire
, 在學(xué)習(xí) AQS 中就說過, 自定義同步器時(shí)需要實(shí)現(xiàn)的幾個(gè)方法.
-
tryAcquire(int arg)
: 獨(dú)占式. 嘗試獲取資源. 成功返回 true, 失敗返回 false. -
tryRelease(int)
: 獨(dú)占式. 嘗試釋放資源. 成功返回 true, 失敗返回 false.
所以這里調(diào)用的是 ReentrantLock.NonfairSync
中重寫的 tryAcquire
方法. 假如返回了 false
, 那么取反得 true
, 就又會(huì)調(diào)用 addWaiter(Node.EXCLUSIVE)
與 acquireQueued()
, 那么還是先來看 tryAcquire()
?
5.4 ReentrantLock.NonfairSync.tryAcquire()
static final class NonfairSync extends Sync {
...
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
nonfairTryAcquire
方法在 Sync 中. 直接進(jìn)入.
?
5.5 ReentrantLock.Sync.nonfairTryAcquire()
//參數(shù)值為 1.
final boolean nonfairTryAcquire(int acquires) {
//獲得當(dāng)前線程對象
final Thread current = Thread.currentThread();
//獲得共享資源狀態(tài),
int c = getState();
//是 0 表示未被占用. 這里判斷的目的是有可能 B 線程剛進(jìn)入, A 線程就執(zhí)行完了. 那么 B 就可以直接占用資源
if (c == 0) {
//占用資源, 改變狀態(tài)為 1.
if (compareAndSetState(0, acquires)) {
//記錄 B 線程.
setExclusiveOwnerThread(current);
return true;
}
}
//判斷當(dāng)前要獲取鎖的線程是不是之前記錄的線程. 也就是判斷是不是重入.
else if (current == getExclusiveOwnerThread()) {
//如果是重入, 那么資源的狀態(tài)值就 +1.
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
//改變狀態(tài)值.
setState(nextc);
return true;
}
return false;
}
B 線程嘗試拿鎖失敗, 那么要怎么辦呢, 下一步應(yīng)該就是要加入到等待隊(duì)列中了.
B 線程執(zhí)行到這里的時(shí)候, 假設(shè) A 線程還在執(zhí)行, 那么 if
與 else if
都不成立, 則直接返回 false
. 在 5.3 中這個(gè)返回值取反為 true
后就會(huì)調(diào)用 acquireQueued
, 參數(shù)是 addWaiter
方法的返回值和 1. 先進(jìn)去看一下 addWaiter
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
?
5.6 AbstractQueuedSynchronizer.addWaiter()
//調(diào)用此方法的時(shí)候, 傳入的 Node 對象為 Node.EXCLUSIVE, 獨(dú)占模式.
//還記得在 AQS 中的內(nèi)部的 Node 中, 獨(dú)占模式的聲明嗎? static final Node EXCLUSIVE = null;
//所以這里傳入的是一個(gè) null.
private Node addWaiter(Node mode) {
//將 B 線程封裝成一個(gè) Node 節(jié)點(diǎn). 模式為獨(dú)占模式.
Node node = new Node(Thread.currentThread(), mode);
//將尾指針賦值給 pred.
Node pred = tail;
//判斷 pred 是否為 null
if (pred != null) {
//不為 null, 就將新節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)指向隊(duì)列的尾部 tail.
node.prev = pred;
//通過 CAS 將新節(jié)點(diǎn)變?yōu)殛?duì)列尾部
if (compareAndSetTail(pred, node)) {
//將之前尾部節(jié)點(diǎn)的后置節(jié)點(diǎn)指向新節(jié)點(diǎn)
pred.next = node;
//返回新節(jié)點(diǎn)
return node;
}
}
// pred = null 說明還沒有初始化頭尾,
enq(node);
//初始化好頭尾并入隊(duì)成功后,返回 B 線程封裝的節(jié)點(diǎn).
return node;
}
//cas 自旋入隊(duì)到尾部
private Node enq(final Node node) {
for (;;) {
Node t = tail;
//隊(duì)列為空, 就創(chuàng)建一個(gè)空節(jié)點(diǎn)(哨兵節(jié)點(diǎn)),并將 head 與 tail 指向它
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else { //第二次自旋就進(jìn)入到 else 中
//新節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)指向第一次自旋時(shí)創(chuàng)建的尾部空節(jié)點(diǎn).因?yàn)槟壳瓣?duì)列中除了哨兵節(jié)點(diǎn)外沒有節(jié)點(diǎn)存在
node.prev = t;
//將尾指針指向新節(jié)點(diǎn)
if (compareAndSetTail(t, node)) {
//將哨兵節(jié)點(diǎn)的后置節(jié)點(diǎn)指向新節(jié)點(diǎn).
t.next = node;
return t;
}
}
}
}
通過前面的 嘗試拿鎖 tryAcquire
和 addWaiter
表示 B線程拿鎖失敗, 已經(jīng)被加入到等待隊(duì)列的隊(duì)尾了, 那么下一步要干什么呢? 那就是再嘗試拿一次鎖, 因?yàn)橛锌赡苓@個(gè)時(shí)間,A 線程執(zhí)行完了. 如果再一次拿鎖失敗, 那么就需要進(jìn)入到阻塞狀態(tài)了, 直到 A 線程釋放鎖然后喚醒 B 線程. acquireQueued
方法就是完成了這幾步的操作, 那么現(xiàn)在接著返回到5.3, 看 acquireQueued
方法
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
?
5.7 AbstractQueuedSynchronizer.acquireQueued
final boolean acquireQueued(final Node node, int arg) {
//表示是否成功獲取資源
boolean failed = true;
try {
//是否中斷
boolean interrupted = false;
//自旋
for (;;) {
//獲取到 B 線程節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn). 也就是哨兵節(jié)點(diǎn).
//為啥不是 A 線程節(jié)點(diǎn)? 因?yàn)?A 線程最先搶占到資源, 都沒有入隊(duì), 直接就執(zhí)行了. 所以隊(duì)列中沒有 A 線程節(jié)點(diǎn).
final Node p = node.predecessor();
//如果前驅(qū)節(jié)點(diǎn)是哨兵節(jié)點(diǎn), 即 B 線程節(jié)點(diǎn)是第二位. 那么就有資格去再次嘗試獲取鎖.
// 這時(shí)候 A 線程如果還沒執(zhí)行完, 那么執(zhí)行 tryAcquire 也就是第5步, 返回的肯定也是 false. 不成立, 所以不進(jìn)入.
// 如果 A 線程在這個(gè)時(shí)刻執(zhí)行完了, 那么執(zhí)行第5步的時(shí)候就會(huì)返回 true. 進(jìn)入 if 內(nèi)
if (p == head && tryAcquire(arg)) {
//拿到鎖后, 將 head 指向 B 線程節(jié)點(diǎn),
// 并將 B 線程節(jié)點(diǎn)中的 node.thread = null,
//同時(shí)斷開B 線程節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)也就是哨兵節(jié)點(diǎn)的引用 node.prev = null.
//所以 head 所指的標(biāo)桿結(jié)點(diǎn),就是當(dāng)前獲取到資源的那個(gè)結(jié)點(diǎn)或null。
setHead(node);
//setHead 中 已經(jīng)將線程 B的節(jié)點(diǎn) node.prev 設(shè)置為 null, 這里再將 head.next 也設(shè)置為 null
// 就是為了方便 GC 回收以前的 head 節(jié)點(diǎn)
p.next = null; // help GC
//成功獲取資源
failed = false;
//返回等待過程中是否被中斷過.
return interrupted;
}
//如果B 線程執(zhí)行到這里的時(shí)候, A 線程未釋放資源, 那么就通過 lockSupport.park 進(jìn)入阻塞狀態(tài). 直到被 unpark 喚醒
// 同時(shí)如果不可中斷的情況下被中斷了, 那么會(huì)從 park 中醒來, 發(fā)現(xiàn)拿不到鎖, 從而繼續(xù)進(jìn)入到 park阻塞狀態(tài).
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
//如果等待中被中斷, 改變 interrupted 標(biāo)志位.
interrupted = true;
}
} finally {
//等待過程中沒有成功獲取資源, 那么取消節(jié)點(diǎn)在隊(duì)列中的等待.
if (failed)
cancelAcquire(node);
}
}
期間調(diào)用了 shouldParkAfterFailedAcquire(哨兵節(jié)點(diǎn), B 線程節(jié)點(diǎn))
通過前驅(qū)節(jié)點(diǎn)判斷當(dāng)前節(jié)點(diǎn)是否需要進(jìn)入阻塞 與 parkAndCheckInterrupt()
阻塞線程方法, 先來看著兩個(gè)方法具體實(shí)現(xiàn)過程.
?
5.8 AbstractQueuedSynchronizer.shouldParkAfterFailedAcquire()
//當(dāng) B 線程執(zhí)行到這里時(shí), 傳入?yún)?shù)為 pred = 哨兵節(jié)點(diǎn), node = B 線程節(jié)點(diǎn)
//如果還有 C 線程, 那么 pred = B 線程節(jié)點(diǎn), node = C 線程節(jié)點(diǎn).
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//獲得前驅(qū)節(jié)點(diǎn)的狀態(tài). 默認(rèn)為 0.
int ws = pred.waitStatus;
// 如果前驅(qū)節(jié)點(diǎn)的狀態(tài)是 Node.SIGNAL, 說明已經(jīng)通知前驅(qū)節(jié)點(diǎn)釋放鎖時(shí)通知當(dāng)前節(jié)點(diǎn), 那么當(dāng)前節(jié)點(diǎn)就可以進(jìn)入阻塞狀態(tài)了. 返回 true.
// 在 4.4 Node 節(jié)點(diǎn)說明中, 已經(jīng)寫明
// Node.SIGNAL 表示后繼節(jié)點(diǎn)在等待當(dāng)前節(jié)點(diǎn)喚醒. 后繼節(jié)點(diǎn)入隊(duì)時(shí), 會(huì)將前繼節(jié)點(diǎn)的狀態(tài)更新為此狀態(tài).
if (ws == Node.SIGNAL)
return true;
// 前驅(qū)節(jié)點(diǎn)狀態(tài) > 0 說明前驅(qū)節(jié)點(diǎn)已經(jīng)放棄獲取鎖了,
// 那么就一直往前找, 直到找到一個(gè)正常等待的狀態(tài)的節(jié)點(diǎn), 并排在它后面,
// 由于那些已經(jīng)放棄獲取鎖的節(jié)點(diǎn), 由于被加塞到它們前面, 那些節(jié)點(diǎn)相當(dāng)于形成了一個(gè)無引用的鏈, 稍后會(huì)被GC 回收.
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
//因?yàn)楫?dāng)前 B 線程節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)是哨兵節(jié)點(diǎn), 狀態(tài)默認(rèn)是 0 ,所以會(huì)執(zhí)行這句代碼.
//把前驅(qū)節(jié)點(diǎn)的狀態(tài),也就是哨兵節(jié)點(diǎn)的狀態(tài)設(shè)置為 -1, 并且返回了 false. 然后在第7步內(nèi),再次進(jìn)行自旋.
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
由于在鎖的使用場景內(nèi), Node
的 waitStatus
初始值必定是 0 , 因此在這方法首次進(jìn)入的時(shí)候, 前驅(qū)節(jié)點(diǎn)的狀態(tài)必定是 0, 所以會(huì)先修改哨兵節(jié)點(diǎn)的狀態(tài)值為 -1. 然后會(huì)返回 false
. 那么接著會(huì)回到 5.7 if
條件內(nèi)的第一個(gè)不成立, 再次進(jìn)行自旋, 如果期間 A 線程也未執(zhí)行完成, 那么會(huì)再次調(diào)用 shouldParkAfterFailedAcquire
方法. 當(dāng)?shù)诙芜M(jìn)入到這個(gè)方法的時(shí)候, if (ws == Node.SIGNAL)
這個(gè)判斷就會(huì)成立, 直接返回 true
. (第一次將前驅(qū)節(jié)點(diǎn)的狀態(tài)設(shè)置為 -1, 第二次進(jìn)入就返回 true ). 那么按照之前說的, 當(dāng)前節(jié)點(diǎn)已經(jīng)可以進(jìn)入阻塞狀態(tài)了, 接著執(zhí)行 5.7 中阻塞線程方法 parkAndCheckInterrupt ()
.
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
//如果等待中被中斷, 改變 interrupted 標(biāo)志位.
interrupted = true;
?
5.9 AbstractQueuedSynchronizer.parkAndCheckInterrupt()
private final boolean parkAndCheckInterrupt() {
//走到這里, 線程 B 才算是被阻塞, 然后等待被喚醒.
LockSupport.park(this);
//被喚醒后, 才會(huì)執(zhí)行這行代碼.
// 被喚醒后, 查看自己是不是被中斷的.
return Thread.interrupted();
}
?
5.10 lock 小結(jié)
至此, B 線程成功入隊(duì)阻塞. 在 5.7 中代碼執(zhí)行到 parkAndCheckInterrupt()
阻塞后, 就暫停了向下執(zhí)行. 等待被喚醒了.
那么現(xiàn)在簡單的總結(jié)一下 acquireQueued()
方法內(nèi)部的流程
- 調(diào)用
lock()
方法, 直接通過 CAS 修改共享資源state
狀態(tài),- 修改成功表示獲取到鎖, 并記錄當(dāng)前線程.
- 修改失敗調(diào)用
acquire ()
獲取鎖.
- 在
acquire ()
獲取鎖方法內(nèi)會(huì)先調(diào)用tryAcquire() --> nonfairTryAcquire()
, 在tryAcquire()
方法返回false
的情況下才會(huì)調(diào)用acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
- 在
nonfairTryAcquire()
內(nèi)先判斷共享資源state
是否為 0 . 為 0 則通過 CAS 修改共享資源為 1, 并記錄當(dāng)前線程. 返回true
. -
state
不為 0, 接著判斷記錄的線程是否是當(dāng)前線程, 這個(gè)判斷表示是否重入, 是重入則共享資源state
值累加 1. 并返回true
. - 在共享資源不為 0, 并且不是重入的情況下, 直接返回
false
.
- 在
-
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
方法內(nèi), 會(huì)先調(diào)用addWaiter()
入隊(duì)-
addWaiter() --> enq()
, 在addWaiter()
方法內(nèi)先將當(dāng)前線程封裝為一個(gè)節(jié)點(diǎn).模式為獨(dú)占模式. - 接著判斷隊(duì)尾
tail
是否為null
, 也就是判斷是否初始化過隊(duì)列.- 如果
tail
不為null
, 通過 CAS 自旋將新節(jié)點(diǎn)加入到隊(duì)尾. 并返回新節(jié)點(diǎn). -
tail
為null
, 調(diào)用enq()
方法自旋初始化head,tail
后將新節(jié)點(diǎn)加入到隊(duì)尾,
- 如果
-
- 入隊(duì)成功后執(zhí)行執(zhí)行
acquireQueued(新節(jié)點(diǎn), 1)
阻塞線程.acquireQueued()
方法也是自旋方法.- 先獲取新節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn),
- 如果前驅(qū)節(jié)點(diǎn)是
head
并且再次嘗試拿鎖成功, 執(zhí)行出隊(duì)和換head
操作. - 前驅(qū)節(jié)點(diǎn)不是
head
或者嘗試拿鎖失敗, 進(jìn)入阻塞判斷方法shouldParkAfterFailedAcquire()
-
shouldParkAfterFailedAcquire()
只會(huì)執(zhí)行 2 次, 最后會(huì)返回true
, 表示可以進(jìn)入阻塞狀態(tài). - 在
acquireQueued()
方法內(nèi)又會(huì)調(diào)用parkAndCheckInterrupt()
方法使用LockSupport.park()
阻塞當(dāng)前線程. 等待被喚醒.
到這里, 獲取鎖的流程已經(jīng)分析完了. 接下來就是釋放鎖與喚醒了.
?
5.11 從 ReentrantLock.unLock() 開始
假如這時(shí)候, A 線程已經(jīng)執(zhí)行完成, 并且調(diào)用了 unLock
那么代碼如下.
public void unlock() {
sync.release(1);
}
平時(shí)我們調(diào)用 unLock
后, 調(diào)用了 Sync.release(1)
方法, 但是 AQS 中 release
方法與 acquire
方法一樣, 都是 final
類型的, 所以執(zhí)行的還是 AbstractQueuedSynchronizer.release(1)
?
5.12 AbstractQueuedSynchronizer.release()
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
這個(gè)方法內(nèi)部和上面分析的 tryAcquire ()
一樣, 都是需要自定義的同步器去實(shí)現(xiàn)的. 我們進(jìn)ReentrantLock.Sync.tryRelease(1)
去看一下.
值得注意的是, 在成功釋放鎖之后( tryRelease
返回 true
之后), 喚醒后繼節(jié)點(diǎn)只是一個(gè) "附加操作", 無論該操作結(jié)果怎樣, 最后 release
操作都會(huì)返回 true
.
?
5.13 ReentrantLock.Sync.tryRelease()
//傳入的值為 1.
protected final boolean tryRelease(int releases) {
//如果這里沒有重入, 那么 getState() 值就為 1, 1-1 =0 , 表示釋放共享資源
int c = getState() - releases;
//釋放鎖的線程當(dāng)前必須是持有鎖的線程
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
//清除記錄的線程
setExclusiveOwnerThread(null);
}
//設(shè)置鎖的狀態(tài)為未占用.
setState(c);
return free;
}
正常來說 tryRelease ()
都會(huì)成功的. 因?yàn)檫@是獨(dú)占模式. 它來釋放鎖, 那么肯定是已經(jīng)拿到鎖了. 直接減掉相應(yīng)量的值即可(state -= arg). 不需要考慮線程安全問題. 那么接著回到 5.12.
public final boolean release(int arg) {
//這里返回了 true.
if (tryRelease(arg)) {
//找到 head
Node h = head;
// h 如果為 null, 說明沒有下一個(gè)結(jié)點(diǎn)了, 因?yàn)槿绻邢乱粋€(gè)實(shí)際的節(jié)點(diǎn)的話, 在入隊(duì)的時(shí)候就會(huì)初始化了 `head, tail`. 雖然是哨兵節(jié)點(diǎn).
//在這里 h != null 成立, 并且, head 的狀態(tài)為-1. 在第8步 shouldParkAfterFailedAcquire 方法設(shè)置的.
if (h != null && h.waitStatus != 0)
//喚醒后置線程節(jié)點(diǎn)
unparkSuccessor(h);
return true;
}
return false;
}
喚醒后繼的條件是 h != null && h.waitStatus != 0
, head
不為 null
且 head
的狀態(tài)不是初始狀態(tài), 則喚醒后置. 在獨(dú)占模式下h.waitStatus
可能等于0,-1.
?
5.14 AbstractQueuedSynchronizer.unparkSuccessor()
//參數(shù)值為 head
private void unparkSuccessor(Node node) {
// head 狀態(tài)為 -1.
int ws = node.waitStatus;
if (ws < 0)
//通過 CAS 修改 head 狀態(tài)為 0.
compareAndSetWaitStatus(node, ws, 0);
// 獲取到 B 線程節(jié)點(diǎn).
Node s = node.next;
//喚醒后繼節(jié)點(diǎn)的線程, 若為空, 從 tail 往后遍歷找一個(gè)距離`head`最近的正常的節(jié)點(diǎn)
//通常情況下, 要喚醒的節(jié)點(diǎn)就是自己的后置節(jié)點(diǎn). 如果后置節(jié)點(diǎn)在等待鎖就直接喚醒.
//但是 上面 5.8 也說過, 也有可能存在后繼節(jié)點(diǎn)放棄等待鎖的情況.
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
//找到正常狀態(tài)的節(jié)點(diǎn), 但是并沒有返回, 而是繼續(xù)向前找.
if (t.waitStatus <= 0)
s = t;
}
//喚醒 B 線程.
if (s != null)
LockSupport.unpark(s.thread);
}
這里有個(gè)疑問, 為什么要從隊(duì)尾逆向向前查找? 而不是直接從 head
開始向后查找呢? 這樣只要正向找到第一個(gè), 是不是就可以停止了. 這里這樣設(shè)計(jì)的原因是為了照顧新入隊(duì)的節(jié)點(diǎn), 這里又會(huì)回到上面的 5.6 addWaiter
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
//將尾指針賦值給 pred.
Node pred = tail;
//判斷 pred 是否為 null
if (pred != null) {
//將新節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)指向隊(duì)列的尾部 tail.
node.prev = pred; //----------------- step1
//通過 CAS 將新節(jié)點(diǎn)變?yōu)殛?duì)列尾部
if (compareAndSetTail(pred, node)) { //----------------- step2
//將之前尾部節(jié)點(diǎn)的后置節(jié)點(diǎn)指向新節(jié)點(diǎn)
pred.next = node; // ----------------- step3
//返回新節(jié)點(diǎn)
return node;
}
}
enq(node);
return node;
}
仔細(xì)觀察可以發(fā)現(xiàn), 節(jié)點(diǎn)入隊(duì)并不是一個(gè)原子操作, 雖然用了 compareAndSetTail
操作保證了將新節(jié)點(diǎn)變?yōu)槲补?jié)點(diǎn), 但是只能保證step1
和 step2
是執(zhí)行完成的. 有可能在執(zhí)行 step3
的時(shí)候, 就有別的線程釋放了鎖調(diào)用了 unparkSuccessor()
方法.那么此時(shí) step3
還沒執(zhí)行, 還未將之前尾節(jié)點(diǎn)的后置節(jié)點(diǎn)指向新節(jié)點(diǎn). 所以如果從前向后遍歷的話, 是遍歷不到我們新加入的節(jié)點(diǎn)的. 還未形成引用關(guān)系.
但是因?yàn)樵?step2
中已經(jīng)將尾節(jié)點(diǎn)設(shè)置成功, 同時(shí)在 step1
中也將新節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)指向了前尾節(jié)點(diǎn). 所以如果從后往前遍歷的話, 新的尾結(jié)點(diǎn)是可以遍歷到, 而且前驅(qū)的前尾結(jié)點(diǎn)也建立了關(guān)系, 可以一直向前查找.
現(xiàn)在 B 線程被喚醒了, 那么接下來的流程是怎么樣的呢. 還記得 B 線程是在哪里被阻塞的嗎? 是在 5.9, 下面是代碼.
private final boolean parkAndCheckInterrupt() {
//走到這里, 線程 B 才算是被阻塞, 然后等待被喚醒.
LockSupport.park(this);
//被喚醒后, 才會(huì)執(zhí)行這行代碼.
// 被喚醒后, 查看自己是不是被中斷的.
return Thread.interrupted();
}
被喚醒后, 執(zhí)行了 Thread.interrupted()
. 我們知道這個(gè)方法函數(shù)返回的是當(dāng)前正在執(zhí)行線程的中斷狀態(tài),并清除它. 因?yàn)?B 線程沒有被中斷, 所以這里返回的是 false
. 接下來又回到了 5.7 acquireQueued
方法中調(diào)用了 parkAndCheckInterrupt()
的 if
判斷
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//B 線程被喚醒后, 再次自旋一次, p 還是為 head 節(jié)點(diǎn).
final Node p = node.predecessor();
//第一個(gè)條件成立, 再次調(diào)用 tryAcquire, 嘗試拿鎖. 這時(shí)候已經(jīng)可以拿到鎖了.state = 0.在上面 5.5 直接就返回了 true.
if (p == head && tryAcquire(arg)) {
//拿到鎖后, 將 head 指向 B 線程節(jié)點(diǎn), 并將 B 線程節(jié)點(diǎn)中的 node.thread = null,
//同時(shí)斷開B 線程節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)也就是哨兵節(jié)點(diǎn)的引用 node.prev = null.
//所以 head 所指的標(biāo)桿結(jié)點(diǎn),就是當(dāng)前獲取到資源的那個(gè)結(jié)點(diǎn)或null。
setHead(node);
//setHead 中 已經(jīng)將線程 B的節(jié)點(diǎn) node.prev 設(shè)置為 null, 這里再將 head.next 也設(shè)置為 null
// 就是為了方便 GC 回收以前的 head 節(jié)點(diǎn)
p.next = null; // help GC
//成功獲取資源
failed = false;
//返回等待過程中是否被中斷過. 沒有被打斷過, 所以返回 false.
return interrupted;
}/
// ------------------------------------直接看這里 -------------------------------------------
//B 線程被喚醒后, parkAndCheckInterrupt() 返回的是 false.那么 if 條件不成立
//方法將再次自旋.
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
//如果等待中被中斷, 改變 interrupted 標(biāo)志位.
interrupted = true;
}
} finally {
//failed= false, 所以不會(huì)取消排隊(duì), 整個(gè)方法結(jié)束.
if (failed)
cancelAcquire(node);
}
}
?
5.15 unlock 小結(jié)
release
方法是獨(dú)占模式下線程釋放鎖的頂層入口, 如果徹底釋放了, 也就是 state = 0
, 它會(huì)喚醒等待隊(duì)列里的其他線程來獲取資源.
- 調(diào)用
unlock
方法后內(nèi)部調(diào)用了AbstractQueuedSynchronizer.release()
方法. 在其方法內(nèi)部又調(diào)用tryRelease
嘗試釋放鎖.一般都會(huì)釋放成功. - 釋放成功后準(zhǔn)備喚醒后繼節(jié)點(diǎn), 但是有一個(gè)喚醒條件, 就是
if (h != null && h.waitStatus != 0)
,h
如果為null
, 說明沒有下一個(gè)節(jié)點(diǎn)了, 因?yàn)槿绻邢乱粋€(gè)實(shí)際的節(jié)點(diǎn)的話, 在入隊(duì)的時(shí)候就會(huì)初始化了head, tail
. 雖然是哨兵節(jié)點(diǎn). 所以在這里h != null
成立, 并且,head
的狀態(tài)為 -1. 在 5.8shouldParkAfterFailedAcquire
方法設(shè)置的. 所以條件成立, 調(diào)用AbstractQueuedSynchronizer.unparkSuccessor()
方法喚醒 - 在喚醒的后置節(jié)點(diǎn)的時(shí)候, 先將
head
的狀態(tài)設(shè)置為 0, 接著從head.next
獲取到要喚醒的線程節(jié)點(diǎn). 調(diào)用LockSupport.unpark(s.thread)
將其喚醒. - 喚醒后代碼又回到了第7步中內(nèi)之前阻塞的地方, 條件不成立, 再次自旋. 嘗試拿鎖成功, 設(shè)置
head
為要喚醒的節(jié)點(diǎn), 也就是換頭出隊(duì). 再將原head
節(jié)點(diǎn)的next
置為null
. 方便 GC 原head
節(jié)點(diǎn). 最后整個(gè)方法結(jié)束.
至此, 從 ReentrantLock
來看 AQS
關(guān)于獨(dú)占鎖部分已經(jīng)分析完了, 有興趣的朋友可以按照這個(gè)思路, 執(zhí)行分析一下 AQS 的共享鎖. 在最后補(bǔ)上之前說的 ReentrantLock
公平鎖與非公鎖的區(qū)別.
?
六. 公平鎖與非公平鎖的區(qū)別
上面說過, 無論有參還是無參的 ReentrantLock
構(gòu)造函數(shù), 構(gòu)建出來的對象, 都會(huì)調(diào)用 lock
方法, 只是根據(jù)創(chuàng)建 ReentrantLock
對象時(shí)傳入?yún)?shù)來決定調(diào)用的是公平鎖的 lock
還是非公平鎖的 lock
.
那么就先看兩者之間 lock
的區(qū)別.
//非公平鎖
static final class NonfairSync extends Sync {
...
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
...
}
//公平鎖
static final class FairSync extends Sync {
...
final void lock() {
acquire(1);
}
這里就能看出一點(diǎn)區(qū)別, 非公平鎖在 lock
的時(shí)候, 都會(huì)先去通過 CAS 改變 state
的值, 看是否能夠成功, 也就是說, 非公平鎖每次都先會(huì)嘗試插隊(duì). 不管能不能插隊(duì)成功, 先插了再說. 如果插隊(duì)失敗, 那就是和公平鎖一樣, 都調(diào)用了 AbstractQueuedSynchronizer. acquire()
方法. 在 AbstractQueuedSynchronizer. acquire()
方法內(nèi), 又都調(diào)用了各自實(shí)現(xiàn)的 tryAcquire()
方法. 那么接著看著兩種鎖各自實(shí)現(xiàn)的 tryAcquire()
.
public class ReentrantLock implements Lock, java.io.Serializable {
//非公平鎖
static final class NonfairSync extends Sync {
..
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)) {
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;
}
...
//公平鎖
static final class FairSync extends Sync {
...
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//區(qū)別在這里
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;
}
}
}
非公平鎖與公平鎖的 tryAcqure()
方法實(shí)現(xiàn), 區(qū)別就在 公平鎖的 if
判斷內(nèi)多了一個(gè)條件. !hasQueuedPredecessors()
!hasQueuedPredecessors()
是什么呢
public final boolean hasQueuedPredecessors() {
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
簡單就是說判斷等待隊(duì)列中是否存在有效的節(jié)點(diǎn), 通過這個(gè)判斷得知公平鎖在lock
的時(shí)候會(huì)先判斷等待隊(duì)列是否有有效的節(jié)點(diǎn)存在, 要拿鎖的線程是否需要排隊(duì).
公平鎖與非公平鎖的區(qū)別
- 在調(diào)用
lock
方法的時(shí)候, 非公平鎖會(huì)先拿一次鎖, 拿不到才去執(zhí)行自己實(shí)現(xiàn)的tryAcquire()
方法. 而公平鎖則會(huì)直接調(diào)用自己實(shí)現(xiàn)的tryAcquire()
. - 在
tryAcquire()
方法內(nèi), 公平鎖會(huì)多一個(gè)判斷, 判斷當(dāng)前等待隊(duì)列中是否存在有效的節(jié)點(diǎn), 看是否需要排隊(duì).
這就是他們的區(qū)別. 后續(xù)流程都基本一致. 本章內(nèi)容到此就結(jié)束了, 如果能看到這里, 說明你的毅力還真是強(qiáng)大. 若對你有所幫助, 請點(diǎn)贊關(guān)注走一波.
以上內(nèi)容如有分析錯(cuò)誤, 還請留言, 大家一起探討.