Java8 源碼閱讀 - AbstractQueuedSynchronizer
Java8 源碼閱讀 - AQS之Condition
Java AQS作為JUC各種鎖的核心類,承擔了制定和管理線程狀態的職責,通過閱讀AQS的代碼可以學習大牛處理并發的思想,畢竟語言只是思想的一種體現,學會了思想自然而然也能觸類旁通;
特性
AQS被設計成大多數同步器(Synchronizer)的基類,提供一個實現阻塞鎖和相關同步器(信號量、事件等),核心依賴于一個FIFO的等待隊列和一個原子性的狀態state
;AQS提供了相關模版方法供子類來決定如何來獲取和釋放這個狀態,比如ReentrantLock
中的內部類Sync和CountDownLatch
的內部類Sync;
AQS分為兩種模式,共享模式(Shared Mode)和獨占模式(Exclusive Mode),當以獨占模式占用鎖時,其他線程將嘗試獲取鎖時會失敗,而在共享模式下是允許多個線程占用鎖;
AQS是一個通過內置的FIFO雙向隊列來完成線程的排隊工作,等待隊列是類CLH鎖隊列;
當一個線程需要獲取鎖時,會創建一個新的節點包含該線程的信息和lock狀態,然后使自己添加到隊列的尾部,然后不停的輪詢前任節點是否釋放鎖直到獲取鎖成功;當每個線程釋放鎖的時候,將當前線程出隊,這就是CLH鎖隊列;
和其他CLH鎖隊列一樣使用的是通用的邏輯,每個節點中的status字段跟蹤線程信息,和其他CLH隊列不同的是,類CLH鎖隊列會去阻塞等待獲取鎖的隊列而不是自旋,隊列的每個節點作為一個特定通知樣式的監視器,它持有一個等待線程;
源碼細節
static final class Node {
/** 標記是共享模式的節點 */
static final Node SHARED = new Node();
/** 標記是排他模式的節點 */
static final Node EXCLUSIVE = null;
/** 表示線程因為中斷或者等待超時,需要從等待隊列中取消等待;*/
static final int CANCELLED = 1;
/** 此節點的后續節點被(或將很快被)阻塞(通過park),因此當前節點在釋放或取消時必須取消其后續節點。
* 為了避免爭用,獲取方法必須首先表明它們需要一個信號,然后重試原子獲取,如果失敗則阻塞。*/
static final int SIGNAL = -1;
/** 此節點當前位于條件隊列上。在傳輸之前,它不會被用作同步隊列節點,此時狀態將被設置為0 */
static final int CONDITION = -2;
/** 一個被釋放的節點應該被傳播到其他節點。這是在doReleaseShared中設置的(僅針對head節點),
* 以確保傳播能夠繼續,即使其他操作已經介入 */
static final int PROPAGATE = -3;
// 可以是0 或者上面的狀態
volatile int waitStatus;
/** 通過prev可以找到該節點的前置節點 */
volatile Node prev;
/** 原本CLH隊列是不維護next節點的,這里維護next節點是為了能過喚醒下一個被阻塞的節點 */
volatile Node next;
/** Node結點內部的SHARED表示的線程是因為獲取共享資源失敗被阻塞添加到隊列中的線程,
* Node中的EXCLUSIVE表示的線程是獲取獨占資源失敗被阻塞添加到隊列中的線程 */
volatile Thread thread;
/** 下一個等待的節點 值通常是SHARED或者EXCLUSIVE */
Node nextWaiter;
/** 如果節點在共享模式下等待,則返回true。*/
final boolean isShared() {
return nextWaiter == SHARED;
}
/** 返回前一個節點,如果為空則拋出NullPointerException。 */
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() {} //默認是SHARED節點
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}
CLH鎖隊列的節點,無論是共享模式還是排他模式都是使用這個節點,內部維護一個waitStatus
來表示當前鎖的狀態;
/** 等待隊列的頭,會延遲初始化。除了初始化之外,
* 它只通過setHead方法進行修改。注意:如果head存在,會保證節點的waitStatus不會是CANCELLED。*/
private transient volatile Node head;
/** 等待隊列的尾部,會延遲初始化。僅通過方法enq修改,所有等待隊列都是添加到尾部。*/
private transient volatile Node tail;
/** volatile修飾的狀態信息state */
private volatile int state;
CLH隊列需要一個虛構的頭節點head
,只有在第一次出現鎖競爭的時候才會初始化創建,可以通過head節點找到第一個排隊的線程,當解鎖時也只需要移動head就可以將線程出隊;在入隊的時候只需要將tail
節點和新增節點替換同時將tail指向新增節點即可,整體的CLH結構如下圖一樣,我們可以通過代碼來論證這點;
排他模式&ReentrantLock
非公平鎖
AQS的排他模式下的使用案例是ReentrantLock
的NonfairSync
;
static final class NonfairSync extends Sync {
...
final void lock() {
//如果通過cas設置state
if (compareAndSetState(0, 1))
//加鎖成功
setExclusiveOwnerThread(Thread.currentThread());
else
//加鎖失敗 出現了鎖競爭 進入等待隊列
acquire(1);
}
//排他模式嘗試獲取資源,成功則返回true,失敗則返回false
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
非公平鎖加鎖的入口,通過CAS嘗試上鎖,如果出現加鎖失敗,則進入下面的流程;
public final void acquire(int arg) {
if (!tryAcquire(arg) && //如果該線程獲取鎖失敗
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
//tryAcquire進入到這里
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) { // 如果當前同步狀態為0 意味著沒有線程占用鎖 就是鎖可能剛被釋放了
if (compareAndSetState(0, acquires)) { // cas嘗試獲取鎖
setExclusiveOwnerThread(current); // 獲取鎖成功
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
// 如果當前線程已經占用鎖了
int nextc = c + acquires;
if (nextc < 0) // 溢出處理
throw new Error("Maximum lock count exceeded");
setState(nextc); // 每重入一次便往state+1
return true;
}
return false; //獲取鎖失敗
}
執行acquire(1)
就意味著鎖已經被占用了,首先會在nonfairTryAcquire
重新獲取同步器狀態state,因為有可能在這時鎖被其他線程釋放了,獲取獲取鎖成功則直接返回;否則會判斷是否是當前線程占用的鎖,這部分是重入鎖的判斷,如果是當前線程會更新state狀態值,否則獲取鎖失敗進入到下面的流程;
private Node addWaiter(Node mode) {
// 這里的mode是Node.EXCLUSIVE也就是null
Node node = new Node(Thread.currentThread(), mode); // 注意這個Node節點保存的nextWaiter是null
Node pred = tail;
if (pred != null) { // 嘗試快速插入;這一步是快速將節點插入隊列尾部
node.prev = pred; // 將構造后的node結點的前驅結點設置為tail
if (compareAndSetTail(pred, node)) { // CAS設置當前的node結點為tail結點
pred.next = node;
//入隊成功 返回
return node;
}
}
// 入隊失敗,走到這里 有兩種情況
// 1. 當前的隊列為null 未被初始化; 2. 上面cas快速入隊失敗;
enq(node);
return node;
}
private Node enq(final Node node) {
// 自旋直到入隊成功
for (;;) {
Node t = tail;
if (t == null) { // 意味著當前隊列為空
if (compareAndSetHead(new Node())) // 建立個空的節點當head
tail = head;
} else {
node.prev = t; // 將新節點的前驅節點設置為tail節點
if (compareAndSetTail(t, node)) { // 通過CAS將node設置為tail
// 如果cas失敗,意味著tail節點已經被其他節點所占用,重新循環添加節點
t.next = node;
return t;
}
}
}
}
addWaiter
中會將當前線程包裝成一個Node節點然后添加到CLH鎖隊列的末尾,在排他模式下Node節點的nextWaiter是null;
final boolean acquireQueued(final Node node, int arg) {
// node節點是排隊到隊尾的節點
boolean failed = true;
try {
boolean interrupted = false;
// 每個需要獲取鎖的線程都需要自旋等待鎖資源
for (;;) {
// 獲取node的前置節點
final Node p = node.predecessor();
// 如果前置節點是頭節點 則嘗試獲取鎖 又回到上面的nonfairTryAcquire方法
if (p == head && tryAcquire(arg)) {
// 該線程獲取鎖成功 將node節點(p的下一任節點)設置為head
// setHead里面將node的線程信息清除 因為head只做哨兵作用
setHead(node);
// 將p節點出隊 釋放p節點
p.next = null; // help GC
failed = false;
return interrupted;
}
// 如果p不是頭節點 或者 該線程獲取鎖失敗
if (shouldParkAfterFailedAcquire(p, node) && // 若p節點的狀態為SIGNAL,則表示p節點允許被阻塞
parkAndCheckInterrupt()) // 如果該線程被阻塞且線程狀態為中斷
// 將中斷標志置為true 這是lock方法處理中斷的方式
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
// 判斷該線程是否應該被阻塞
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
// 獲取前置節點p的status
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
// 如果前置節點p的狀態為SIGNAL,即表示需要將線程阻塞
return true;
if (ws > 0) {
// 如果前置節點的狀態為CANCELLED,表示線程中斷或者等待超時
// 需要將隊列中節點取消等待
do {
// 回溯找到第一個不為CANCELLED的節點并賦值給pred
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// 即不是SIGNAL也不是CANCELLED狀態
// 將前置節點p的狀態設置為SIGNAL
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
// 將當前線程阻塞并且返回該線程狀態是否是中斷
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
acquireQueued
方法在排他模式下會以自旋的方式且不可中斷的獲取鎖;每一個新加入CLH隊列的線程都會判斷自己的前置節點是否是head節點,只有前置節點是head節點時才有資格去嘗試獲取鎖資源,否則將會把自己阻塞;
// 另一種加鎖方式
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
public final boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException {
if (Thread.interrupted())
// 如果線程中斷 將拋出異常 因為中斷的線程獲取鎖沒有意義
throw new InterruptedException();
return tryAcquire(arg) || // tryAcquire會嘗試獲取鎖一次 失敗將進入到下面
doAcquireNanos(arg, nanosTimeout);
}
static final long spinForTimeoutThreshold = 1000L; // 1000納秒
private boolean doAcquireNanos(int arg, long nanosTimeout) throws InterruptedException {
if (nanosTimeout <= 0L)
return false;
final long deadline = System.nanoTime() + nanosTimeout;
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return true;
}
nanosTimeout = deadline - System.nanoTime();
if (nanosTimeout <= 0L)
// 增加了超時判斷
return false;
if (shouldParkAfterFailedAcquire(p, node) &&
// 如果剩下的時間 > 1000 納米
nanosTimeout > spinForTimeoutThreshold)
// 將短暫掛起
LockSupport.parkNanos(this, nanosTimeout);
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
tryLock
方法無論在公平鎖和非公平鎖下都將采用非公平策略,調用該方法的線程會立即嘗試加鎖,如果加鎖失敗,會在doAcquireNanos
里面自旋等待鎖資源,基本的排隊和掛起邏輯和acquireQueued
類似,但是增加了超時的處理機制,不同的是該方法里面有超時邏輯的判斷,且自旋的過程中是不允許線程中斷的,否則會拋出InterruptedException
異常;
private void cancelAcquire(Node node) {
if (node == null)
return; // 忽略null節點
node.thread = null; // 釋放線程
Node pred = node.prev;
// 將所有前置節點的waitStatus為CANCELLED的節點移除隊列
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
// pred節點指向的是隊列中第一個CANCELLED節點的前置節點
Node predNext = pred.next;
node.waitStatus = Node.CANCELLED;
// 如果當前節點是尾節點且cas將pred節點設置為tail節點成功
if (node == tail && compareAndSetTail(node, pred)) {
// 已經將當前節點移除隊列
// cas將pred節點的next設為null
compareAndSetNext(pred, predNext, null);
} else {
// node節點不是尾節點
int ws;
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
// 如果pred節點不是head 且
// pred節點的狀態為SIGNAL,意味著pred節點準備釋放資源
Node next = node.next;
// 如果當前節點的next節點的狀態不為CANCELLED狀態
// 將p節點的下一個節點設置為next
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
// 若當前節點是head節點 或者
// pred節點的狀態不為SIGNAL
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
private void unparkSuccessor(Node node) {
// 這里的node節點即為head節點
int ws = node.waitStatus;
if (ws < 0)
// 嘗試恢復狀態
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next; // 獲取head的后繼節點 即隊列中第一個節點
if (s == null || s.waitStatus > 0) {
// 如果s節點不為null 但是s節點的狀態為CANCELLED,需要將其釋放
s = null;
// //從tail結點向前遍歷,找到離head最近不為null的節點并且waitStatus不為CANCELLED
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
// 如果找到節點s,則喚醒該結點的線程
LockSupport.unpark(s.thread);
}
當doAcquireNanos
中獲取鎖資源超時了,就會進入到cancelAcquire
中取消正在進行的獲取操作,首先會找到CLH隊列中第一個狀態為CANCELLED
的節點的前置節點p,這里分兩個情況,
- 如果p節點不是head節點,意味著排隊等待的線程大于2(當前線程+其他線程),若p的狀態為
SIGNAL
,且當前線程節點n的下一個節點狀態不為CANCELLED
,會將n的下一個節點銜接到p的下一個節點上去(將n移除隊列),并釋放掉當前線程節點; - 如果p節點是head節點,意味著當前排隊線程是第一個排隊獲取鎖的節點那么再釋放掉當前線程節點前還會去嘗試尋找下一個可以用來喚醒的線程,如果這個節點存在,調用unpark()方法喚醒線程,這里的下一個線程不一定是當前節點的next節點;
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
public final void acquireInterruptibly(int arg) throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (!tryAcquire(arg)) // 若獲取鎖失敗
doAcquireInterruptibly(arg);
}
private void doAcquireInterruptibly(int arg) throws InterruptedException {
final Node node = addWaiter(Node.EXCLUSIVE); // 加入等待隊列
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
// 獲取鎖成功
setHead(node);
p.next = null; // help GC
failed = false;
return;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
// 將線程阻塞后遇到線程中斷情況將拋出線程中斷異常
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
lockInterruptibly
和lock
唯一的區別就是對待線程中斷的處理方式,lock
方法遇到線程中斷選擇的是生吞中斷事件,繼續執行阻塞加鎖操作,等到獲取到鎖的時候才會返回線程是否中斷,而lockInterruptibly
遇到中斷事件時會馬上拋出中斷異常然后退出獲取鎖資源;
公平鎖
static final class FairSync extends Sync {
final void lock() {
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 當前鎖沒有被占用
// 公平鎖和非公平鎖最大的區別在這
if (!hasQueuedPredecessors() && // 沒有其他線程排隊等待
compareAndSetState(0, acquires)) { // cas加鎖成功
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;
}
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
公平鎖在加鎖過程中和非公平鎖的區別是,公平鎖在嘗試加鎖時會判斷當前鎖是否被占用以及排隊隊列前是否有其他先到的排隊線程,非公平鎖則允許每個線程都有一次插隊的機會;
public void unlock() { sync.release(1); }
public final boolean release(int arg) {
if (tryRelease(arg)) {
// 釋放鎖成功
Node h = head;
if (h != null && h.waitStatus != 0)
// 嘗試喚起下一個繼任者
unparkSuccessor(h);
return true;
}
return false;
}
// 返回true表示鎖資源被釋放 否則表示鎖還是被占用
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狀態,如果當前線程已經不再占用任何資源,會嘗試喚起下一個繼任者;
總結
總結下ReentrantLock
的加鎖,ReentrantLock
分為公平鎖和非公平鎖,默認的構造器使用的是非公平鎖,非公平鎖不保證任何特定的訪問順序,可以允許線程插隊獲取鎖,而公平鎖是按照FIFO的順序排隊等待鎖,所以在通常情況下非公平鎖的效率是要比公平鎖高,公平鎖也可能出現線程餓死的情況,即某一個線程持續獲取資源而導致其他線程一直被阻塞,相比于synchronized
,ReentrantLock
提供了一些高級的功能,比如
- 等待鎖中斷
指如果當前持有鎖線程長期不釋放鎖資源,正在等待的線程可以選擇放棄等待,可中斷特性對于處理執行時間長的同步塊很有幫助 - 公平鎖實現
synchronized
默認是非公平鎖,公平鎖可以保證多線程下申請鎖是按照時間先后的順序來的 - 實現鎖綁定多個條件
ReentrantLock
可以綁定多個Condition
對象實現條件的分離和關聯,而synchronized
不行;
內部通過CLH隊列維持各個線程的等待關系,同步器內的state狀態表示的是目前占用鎖的資源數;
共享模式&CountDownLatch
CountDownLatch
是一個同步輔助類,它允許一個或多個線程一直等待直到其他線程執行完畢才開始執行。其中構造器的count表示需要等待執行的線程數;
private static final class Sync extends AbstractQueuedSynchronizer {
Sync(int count) { setState(count); }
int getCount() { return getState(); }
...
}
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
在CountDownLatch
中AQS的state的作用是表示需要等待執行完的線程個數;
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
// state此時大于0 有剩余資源還在等待
doAcquireSharedInterruptibly(arg);
}
// 當資源為0時表示沒有任何資源可以被獲取
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
private void doAcquireSharedInterruptibly(int arg) throws InterruptedException {
// 所有即將被阻塞的線程都會添加到隊列里面
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
// 第一個調用await的會走到這里
int r = tryAcquireShared(arg);
// r=-1時表示state不等于0,反之r=1時state等于0
if (r >= 0) {
// state為0時首節點的線程會被喚醒 然后執行下面的方法
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
// 將自己阻塞等待其他線程喚醒
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
// 支持中斷響應
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
因為初始化時定義了state代表可共享鎖資源的數量,所以在state不為0時執行函數await
的線程都會被阻塞,直到state為0;
public void countDown() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
// 當所有占用的資源都被釋放 喚醒等待隊列的線程
doReleaseShared();
return true;
}
return false;
}
// 只有在state為0時才返回true
protected boolean tryReleaseShared(int releases) {
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
// 通過cas改變state
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
// 當state為0時進入到這個方法
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
// CLH隊列中有剩余的節點 即存在有被阻塞的線程
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
// 保證線程安全的關鍵
continue;
// 喚醒第一個等待節點的線程
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue;
}
if (h == head)
break; // 一次只釋放一個線程
}
}
private void unparkSuccessor(Node node) {
// 這時候進來的node節點waitStatus都是0
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next; // 獲得s的后繼結點,這里即head的后繼結點也就是第一個使用await的線程
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
// 恢復被阻塞線程
LockSupport.unpark(s.thread);
}
// 等到被阻塞線程被喚醒后 會繼續執行方法剩余的部分 并退出函數
private void doAcquireSharedInterruptibly(int arg) throws InterruptedException {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
// r=-1時表示state不等于0,反之r=1時state等于0
if (r >= 0) {
// state為0時首節點的線程會被喚醒 然后執行下面的方法
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
// 如果node節點的waitStatus為0,則將其設置為SIGNAL, 并返回false
// 如果node節點的waitStatus為SIGNAL,則返回true
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private void setHeadAndPropagate(Node node, int propagate) {
// node為head的next節點,propagate為1
Node h = head;
setHead(node); // 將node的thread引用釋放
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
// 獲取node節點的下一個節點nextNode
Node s = node.next;
// 如果nextNode是共享模式或者nextNode是最后一個節點
if (s == null || s.isShared())
// 循環喚醒剩余節點
doReleaseShared();
}
}
countDown
方法通過修改state的值來釋放資源,當全部資源釋放掉時(state == 0)會喚醒隊列中所有被阻塞的線程,注意這里的喚醒操作,所有的喚醒操作都是從隊列的第一個節點開始喚醒,假如有多個線程同時進入到doReleaseShared
釋放也只會有一個線程進行喚醒,這里是通過cas設置節點的waitStatus
的狀態來保證的線程安全的;
總結
CountDownLatch
代碼量較少所以總體而言還是比較簡單的,到這里已經大概了解了共享鎖和排他鎖的現實方式,可以簡單對比一下兩者的區別;
獨占鎖顧名思義就是同時只能一個線程能夠獲取到鎖,其余的線程只能排隊等待鎖資源,在AQS中的CLH隊列只有第一個節點是有權限獲取到鎖的,其他的節點只能被阻塞等待前置節點的喚醒才能加鎖,其中獨占鎖分為公平鎖和非公平鎖,公平鎖就是獲取鎖的順序嚴格按照FIFO的順序,而非公平鎖則不一定是按照FIFO,允許其他線程有搶占資源的能力,通常而言非公平鎖的效率較高;
共享鎖就是允許多個線程都能獲取到鎖資源,在AQS的CLH隊列中所有節點都是已經獲取到鎖的線程,在CountDownLatch
中共享鎖的體現是可以允許多個線程同時入隊被阻塞等待,等資源全部釋放后再從隊列的第一個線程開始逐個喚醒;
Semaphore
信號量機制的Java實現版本,如果說大學時學過操作系統這門課程的同學應該都會有印象,Semaphore通常用于限制能夠訪問某些(物理或邏輯)資源的線程數,內部維護了一個許可證集合,有多少資源需要限制就維護多少許可證集合,每一個線程獲取許可證就調用acquire方法且會被阻塞,直到獲得許可,release方法用來恢復許可并可能釋放一個潛在被阻塞的線程;Semaphore也是分為公平鎖和非公平鎖兩個版本。
非公平鎖
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
// 如果可用的資源數小于0 將其阻塞
doAcquireSharedInterruptibly(arg);
// 否則直接當作加鎖成功
}
// tryAcquireShared 實際上走到這里
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
// 獲取剩下可用的資源數
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
// 若可用的資源數小于0 直接返回
// 否則通過cas更新占用的資源數(state - 1)
return remaining;
}
}
private void doAcquireSharedInterruptibly(int arg) throws InterruptedException {
// 所有即將被阻塞的線程都會添加到隊列里面
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
// 如果可用資源數大于0(r) 則喚醒等待的線程
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
// 將自己阻塞等待其他線程喚醒
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
// 支持中斷響應
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
public void release() { sync.releaseShared(1); }
protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases;
if (next < current)
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
return true;
}
}
非公平鎖的加鎖流程比較簡單,在有剩余資源的情況下只需要一次簡單的cas操作就可以獲取到鎖資源,當剩余資源等于0時才會將線程阻塞,
釋放資源時也是通過cas更新state狀態,在doReleaseShared
中如果有等待的線程會將等待線程喚醒,流程和上面的CountDownLatch
基本一致;
protected int tryAcquireShared(int acquires) {
for (;;) {
// 如果等待隊列中有線程等待,則將該線程直接加入隊列,不會嘗試獲取鎖資源
if (hasQueuedPredecessors())
return -1;
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
公平鎖與非公平鎖的主要區別在于公平鎖發現等待隊列中有其他線程等待資源后會自動添加到隊列末尾并將自己阻塞,少了搶占鎖資源這一步;