目錄
AbstractQueuedSynchronizer,簡稱AQS。是一個用於構建鎖和同步器的框架,許多同步器都可以通過AQS很容易並且高效地構造出來,如常用的
ReentrantLock
、Semaphore
、CountDownLatch
等。基於AQS來構建同步器能帶來許多好處。它不僅能極大地減少實現工作,而且也不必處理在多個位置上發生的競爭問題。在基於AQS構建的同步器中,只可能在一個時刻發生阻塞,從而降低上下文切換的開銷,並提高吞吐量。Doug Lea 大神在設計AQS時也充分考慮了可伸縮性,因此java.util.concurrent中所有基於AQS構建的同步器都能獲得這個優勢。大多數開發者都不會直接使用AQS,JUC中標準同步器類都能夠滿足絕大多數情況的需求。但如果能瞭解標準同步器類的實現方式,那麼對理解它們的工作原理是非常有幫助的。
一、CountDownLatch的應用場景
1、做併發性能測試
這是一種真正意義上的併發,不再是啓動一個線程,調用一下start()方法了,而是開啓線程後,讓線程在同一起跑線上開始競爭資源,測試代碼的併發能力。
2、多線程執行任務,最後彙總
由於業務比較複雜,每個功能比較獨立而且十分耗時,但是條件是等待每個功能都執行完畢後,主線程才能繼續向下執行。
針對於以上使用背景,我們可以模擬一種場景,將上述的兩種情況都覆蓋到:
情景模擬
背景:
今年學校組織了一場運動會,其中有一個項目是1000米跑步比賽。
參賽人員:
在經過一系列的比賽過後,最後決賽就只剩下了5個人,他們分別是:
張三
李四
王五
趙六
* 田七
他們開始了最終的決賽。
比賽要求:
1、聽發令槍後開始跑。
2、不得搶跑。
3、最後由裁判統計好結果後比賽結束。
上代碼:
運動員類:
package org.hcgao.common.blog.AQS.countdownlatch;
import java.util.concurrent.CountDownLatch;
public class Sportsman implements Runnable {
private String sportsmanName;
// 發令槍信號
private CountDownLatch firingGunSignal;
// 賽場所有運動員都到達目的地
private CountDownLatch answer;
public Sportsman(String sportsmanName, CountDownLatch firingGunSignal, CountDownLatch answer) {
this.sportsmanName = sportsmanName;
this.firingGunSignal = firingGunSignal;
this.answer = answer;
}
@Override
public void run() {
try {
System.out.println("選手--" + sportsmanName + "--準備就緒,正在等待裁判發佈口令");
firingGunSignal.await();
System.out.println("選手--" + sportsmanName + "--已接受裁判口令");
long time = (long) (Math.random() * 10000);
Thread.sleep(time);
System.out.println("選手--" + sportsmanName + "--到達終點, 用時 : "+ time + "毫秒");
answer.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
調用方法:
package org.hcgao.common.blog.AQS.countdownlatch;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 背景:
* 今年學校組織了一場運動會,其中有一個項目是1000米跑步比賽。
* 參賽人員:
* 在經過一系列的比賽過後,最後決賽就只剩下了5個人,他們分別是:
* 張三
* 李四
* 王五
* 趙六
* 田七
* 他們開始了最終的決賽。
*比賽要求:
* 1、聽發令槍後開始跑。
* 2、不得搶跑。
* 3、最後由裁判統計好結果後比賽結束。
*
* @author gaohaicheng123
*
*/
public class CountdownLatchGun {
public static void main(String[] args) {
ExecutorService service = Executors.newCachedThreadPool();
final CountDownLatch firingGunSignal = new CountDownLatch(1);
final CountDownLatch answer = new CountDownLatch(5);
Sportsman zhangsan = new Sportsman("張三", firingGunSignal, answer);
Sportsman lisi = new Sportsman("李四", firingGunSignal, answer);
Sportsman wangwu = new Sportsman("王五", firingGunSignal, answer);
Sportsman zhaoliu = new Sportsman("趙六", firingGunSignal, answer);
Sportsman tianqi = new Sportsman("田七", firingGunSignal, answer);
service.execute(zhangsan);
service.execute(lisi);
service.execute(wangwu);
service.execute(zhaoliu);
service.execute(tianqi);
try {
Thread.sleep((long) (Math.random() * 10000));
System.out.println("裁判"+Thread.currentThread().getName()+"即將發射信號槍");
firingGunSignal.countDown();
System.out.println("裁判"+Thread.currentThread().getName()+"已發信號槍,正在等待所有選手到達終點");
answer.await();
System.out.println("所有運動員均到達了目的地,裁判得到了他們的比賽數據。");
System.out.println("裁判"+Thread.currentThread().getName()+"彙總成績,進行排名,頒獎,比賽結束");
} catch (InterruptedException e) {
e.printStackTrace();
}
service.shutdown();
}
}
執行結果:
選手王五準備就緒,正在等待裁判發佈口令
選手張三準備就緒,正在等待裁判發佈口令
選手趙六準備就緒,正在等待裁判發佈口令
選手李四準備就緒,正在等待裁判發佈口令
選手田七準備就緒,正在等待裁判發佈口令
裁判main即將發射信號槍
選手王五已接受裁判口令
選手張三已接受裁判口令
選手李四已接受裁判口令
選手趙六已接受裁判口令
選手田七已接受裁判口令
裁判main已發信號槍,正在等待所有選手到達終點
選手趙六到達終點, 用時 : 225毫秒
選手王五到達終點, 用時 : 492毫秒
選手張三到達終點, 用時 : 1153毫秒
選手田七到達終點, 用時 : 4516毫秒
選手李四到達終點, 用時 : 6936毫秒
所有運動員均到達了目的地,裁判得到了他們的比賽數據。
裁判main彙總成績,進行排名,頒獎,比賽結束
二、分析一下CountDownLatch底層實現
CountDownLatch我們如何接近你:
JDK1.8 CountDownLatch
源碼:
public class CountDownLatch {
/**
* Synchronization control For CountDownLatch.
* Uses AQS state to represent count.
*/
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
Sync(int count) {
setState(count);
}
int getCount() {
return getState();
}
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
private final Sync sync;
/**
* Constructs a {@code CountDownLatch} initialized with the given count.
*
* @param count the number of times {@link #countDown} must be invoked
* before threads can pass through {@link #await}
* @throws IllegalArgumentException if {@code count} is negative
*/
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
/**
* Causes the current thread to wait until the latch has counted down to
* zero, unless the thread is {@linkplain Thread#interrupt interrupted}.
*
* <p>If the current count is zero then this method returns immediately.
*
* <p>If the current count is greater than zero then the current
* thread becomes disabled for thread scheduling purposes and lies
* dormant until one of two things happen:
* <ul>
* <li>The count reaches zero due to invocations of the
* {@link #countDown} method; or
* <li>Some other thread {@linkplain Thread#interrupt interrupts}
* the current thread.
* </ul>
*
* <p>If the current thread:
* <ul>
* <li>has its interrupted status set on entry to this method; or
* <li>is {@linkplain Thread#interrupt interrupted} while waiting,
* </ul>
* then {@link InterruptedException} is thrown and the current thread's
* interrupted status is cleared.
*
* @throws InterruptedException if the current thread is interrupted
* while waiting
*/
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
/**
* Causes the current thread to wait until the latch has counted down to
* zero, unless the thread is {@linkplain Thread#interrupt interrupted},
* or the specified waiting time elapses.
*
* <p>If the current count is zero then this method returns immediately
* with the value {@code true}.
*
* <p>If the current count is greater than zero then the current
* thread becomes disabled for thread scheduling purposes and lies
* dormant until one of three things happen:
* <ul>
* <li>The count reaches zero due to invocations of the
* {@link #countDown} method; or
* <li>Some other thread {@linkplain Thread#interrupt interrupts}
* the current thread; or
* <li>The specified waiting time elapses.
* </ul>
*
* <p>If the count reaches zero then the method returns with the
* value {@code true}.
*
* <p>If the current thread:
* <ul>
* <li>has its interrupted status set on entry to this method; or
* <li>is {@linkplain Thread#interrupt interrupted} while waiting,
* </ul>
* then {@link InterruptedException} is thrown and the current thread's
* interrupted status is cleared.
*
* <p>If the specified waiting time elapses then the value {@code false}
* is returned. If the time is less than or equal to zero, the method
* will not wait at all.
*
* @param timeout the maximum time to wait
* @param unit the time unit of the {@code timeout} argument
* @return {@code true} if the count reached zero and {@code false}
* if the waiting time elapsed before the count reached zero
* @throws InterruptedException if the current thread is interrupted
* while waiting
*/
public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
/**
* Decrements the count of the latch, releasing all waiting threads if
* the count reaches zero.
*
* <p>If the current count is greater than zero then it is decremented.
* If the new count is zero then all waiting threads are re-enabled for
* thread scheduling purposes.
*
* <p>If the current count equals zero then nothing happens.
*/
public void countDown() {
sync.releaseShared(1);
}
/**
* Returns the current count.
*
* <p>This method is typically used for debugging and testing purposes.
*
* @return the current count
*/
public long getCount() {
return sync.getCount();
}
/**
* Returns a string identifying this latch, as well as its state.
* The state, in brackets, includes the String {@code "Count ="}
* followed by the current count.
*
* @return a string identifying this latch, as well as its state
*/
public String toString() {
return super.toString() + "[Count = " + sync.getCount() + "]";
}
}
CountDownLatch中的
核心方法
首先看一下 CountDownLatch中的內部類,是一個繼承了AQS的內部類。
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
Sync(int count) {
setState(count);
}
int getCount() {
return getState();
}
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
其中 CountDownLatch的構造方法 入參最後的賦值給
private volatile int state;
從例子中我們知道:CountDownLatch answer = new CountDownLatch(5);
state = 5;1、每一次 answer#countDown();方法調用 state會-1
2、線程調用 await()時線程發生阻塞。
1、await()方法:
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
其實是調用了AbstractQueuedSynchronizer #acquireSharedInterruptibly 方法
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
// state狀態變量比較
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
首先判斷是否被中斷,中斷就拋出異常,的話與tryAcquireShared(arg)的返回值相比較,具體實現如下
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
如果此時 state == 0,則繼續執行,如果state >0,則進入阻塞的功能 : doAcquireSharedInterruptibly(int arg) 。
/**
* Acquires in shared interruptible mode.
* @param arg the acquire argument
*/
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.SHARED); //該函數 用於將當前線程相關的節點將入鏈表尾部
boolean failed = true;
try {
for (;;) { //將入無限for循環
final Node p = node.predecessor(); //獲得它的前節點
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) { //唯一退出條件,也就是await()方法返回的條件非常重要!!
setHeadAndPropagate(node, r); //該方法很關鍵具體下面分析
p.next = null; // help GC
failed = false;
return; //到這裏返回
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())// 先知道線程由該函數來阻塞的的
throw new InterruptedException();
}
} finally {
if (failed) //如果失敗或出現異常,失敗 取消該節點,以便喚醒後續節點
cancelAcquire(node);
}
}
addWaiter進行具體的剖析:
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode); //首先 new 首先創建一個新節點,並將當前線程實例封裝在內部,該節點維持一個線程引用
// Try the fast path of enq; backup to full enq on failure
Node pred = tail; //獲取tail節點,tail是volatile型的
if (pred != null) { //不爲空
node.prev = pred;
if (compareAndSetTail(pred, node)) { //利用CAS設置,允許失敗,後面有補救措施
pred.next = node;
return node;
}
}
enq(node); //設置失敗,表明是第一個創建節點,或者是已經被別的線程修改過了會進入這裏
return node;
}
再次進入 enq 進行具體的剖析:
上述方法設置未節點失敗,表明是第一個創建節點,或者是已經被別的線程修改過了會進入這裏。
仍然使用 自旋 for (;;) { ... },直到設置成功纔可以推出方法。
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize 第一個創建時爲節點爲空
if (compareAndSetHead(new Node()))
tail = head; //初始化時 頭尾節點相等
} else {
node.prev = t;
if (compareAndSetTail(t, node)) { //注意這裏,只有設置成功纔會退出,所以該節點一定會被添加
t.next = node;
return t;
}
}
}
}
繼續分析最上面的那個方法 doAcquireSharedInterruptibly(int arg) 中的內容:
for (;;) {
final Node p = node.predecessor();
if (p == head) {/**
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
*/
int r = tryAcquireShared(arg); // 此處仍然是比較 state狀態
if (r >= 0) { // 退出 自 旋 的唯一機會
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
看一下紅色字體部分 if ( shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt() ) :
shouldParkAfterFailedAcquire(p, node)方法:
/*
可以看到針對前驅結點pred的狀態會進行不同的處理
1.pred狀態爲SIGNAL,則返回true,表示要阻塞當前線程。
2.pred狀態爲CANCELLED,則一直往隊列頭部回溯直到找到一個狀態不爲CANCELLED的結點,將當前節點node掛在這個結點的後面。
3.pred的狀態爲初始化狀態,此時通過compareAndSetWaitStatus(pred, ws, Node.SIGNAL)方法將pred的狀態改爲SIGNAL。
!!! 其實這個方法的含義很簡單,就是確保當前結點的前驅結點的狀態爲SIGNAL,
SIGNAL意味着線程釋放鎖後會喚醒後面阻塞的線程。畢竟,只有確保能夠被喚醒,當前線程才能放心的阻塞。
但是要注意只有在前驅結點已經是SIGNAL狀態後纔會執行後面的方法立即阻塞,對應上面的第一種情況。其他兩種情況則因爲返回false而重新執行一遍
for循環。這種延遲阻塞其實也是一種高併發場景下的優化,試想我如果在重新執行循環的時候成功獲取了鎖,是不是線程阻塞喚醒的開銷就省了呢?
*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus; // 獲取前節點的狀態
if (ws == Node.SIGNAL) // 狀態爲SIGNAL -1 表明前節點可以運行
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
/*
* 如果不是則首先遍歷node鏈條找到狀態是0的節點,然後把我們新加進來的node變爲這個節點的下一個節點,然後更新這個0的節點狀態爲-1.
*/
if (ws > 0) { //狀態爲CANCELLED, 如果前節點狀態大於0表明已經中斷,
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else { //狀態爲初始化狀態(ReentrentLock語境下)
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL); //等於0進入這裏
}
return false; // 只有前節點狀態爲Node.SIGNAL才返回真
}
對shouldParkAfterFailedAcquire來進行一個整體的概述,首先應該明白節點的狀態,節點的狀態是爲了表明當前線程的良好度,如果當前線程被打斷了,在喚醒的過程中是不是應該忽略該線程,這個狀態標誌就是用來做這個的具體有如下幾種:
1、線程已經被取消
/** waitStatus value to indicate thread has cancelled */
static final int CANCELLED = 1;2、線程需要去被喚醒
/** waitStatus value to indicate successor's thread needs unparking */
static final int SIGNAL = -1;3、線程正在喚醒等待條件
/** waitStatus value to indicate thread is waiting on condition */
static final int CONDITION = -2;4、線程的共享鎖應該被無條件傳播
/**
* waitStatus value to indicate the next acquireShared should //
* unconditionally propagate
*/
static final int PROPAGATE = -3;
注意:
線程狀態 大於 0 時表明該線程已近被取消,已近是無效節點,不應該被喚醒,注意:初始化鏈頭節點時頭節點狀態值爲0。
當該函數 shouldParkAfterFailedAcquire 返true時 線程調用 parkAndCheckInterrupt 這個阻塞自身。到這裏基本每個調用await函數都阻塞在這裏 (很關鍵哦,應爲下次喚醒,從這裏開始執行哦)
/*
* 在這個方法裏如果返回true,則執行parkAndCheckInterrupt()方法:
* 這裏首先調用LockSupport的park方法把線程寄存,然後在判斷線程的狀態,
* 使用interrupted和interrupt方法的區別還記得嗎?如果調用了interrupted方法,
* 會取消線程的中斷狀態。如果成功,則線程安全的寄存,如果寄存失敗,則返回線程的中斷狀態並取消這個中斷狀態。
*/
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this); //到這裏就完成了線程的等待,這裏的核心是調用了park方法實現的。
return Thread.interrupted();
}
Java中interrupt、interrupted和isInterrupted的關係與區別:
1、interrupt()方法
調用interrupt()方法僅僅是在當前線程中打了一個停止的標記,並不是真的停止線程,需要用戶自己去監視線程的狀態爲並做處理。這一方法實際上完成的是,在線程受到阻塞時拋出一箇中斷信號,這樣線程就得以退出阻塞的狀態。更確切的說,如果線程被Object.wait, Thread.join和Thread.sleep三種方法之一阻塞,那麼,它將接收到一箇中斷異常(InterruptedException),從而提早地終結被阻塞狀態。
public void interrupt() { if (this != Thread.currentThread()) checkAccess(); synchronized (blockerLock) { Interruptible b = blocker; if (b != null) { interrupt0(); // Just to set the interrupt flag b.interrupt(this); return; } } interrupt0(); }
2、interrupted()方法
interrupted():測試當前線程(當前線程是指運行interrupted()方法的線程)是否已經中斷,且清除中斷狀態。public static boolean interrupted() { return currentThread().isInterrupted(true); }
3、
isInterrupted()方法
isInterrupted():測試線程(調用該方法的線程)是否已經中斷,不清除中斷狀態。
public boolean isInterrupted() { return isInterrupted(false); }
interrupted和 isInterrupted兩個方法有兩個主要區別:
interrupted 是作用於當前線程,isInterrupted 是作用於調用該方法的線程對象所對應的線程。(線程對象對應的線程不一定是當前運行的線程。例如我們可以在A線程中去調用B線程對象的isInterrupted方法。)
這兩個方法最終都會調用同一個方法——isInterrupted(boolean ClearInterrupted),只不過參數 ClearInterrupted 固定爲一個是true,一個是false;下面是該方法,該方法是一個本地方法。private native boolean isInterrupted(boolean ClearInterrupted);
測試當前線程是否已經中斷。線程的中斷狀態由該方法清除。換句話說,如果連續兩次調用該方法,則第二次調用將放回false(在第一次調用已經清除了其中斷的狀態之後,且第二次調用檢驗完中斷狀態之前,當前線程再次中斷的情況除外)。
park(Object blocker)方法進行阻塞等待喚醒:
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, 0L);
setBlocker(t, null);
}
線程的掛起和恢復#
sun.misc.Unsafe 下的類 Unsafe,對線程的操作:
unpark#
public native void unpark(Object thread);
釋放被
park
創建的在一個線程上的阻塞。這個方法也可以被使用來終止一個先前調用park
導致的阻塞。這個操作是不安全的,因此必須保證線程是存活的(thread has not been destroyed)。從Java代碼中判斷一個線程是否存活的是顯而易見的,但是從native代碼中這機會是不可能自動完成的。park#
public native void park(boolean isAbsolute, long time);
阻塞當前線程直到一個
unpark
方法出現(被調用)、一個用於unpark
方法已經出現過(在此park方法調用之前已經調用過)、線程被中斷或者time時間到期(也就是阻塞超時)。在time非零的情況下,如果isAbsolute爲true,time是相對於新紀元之後的毫秒,否則time表示納秒。這個方法執行時也可能不合理地返回(沒有具體原因)。併發包java.util.concurrent中的框架對線程的掛起操作被封裝在LockSupport類中,LockSupport類中有各種版本pack方法,但最終都調用了Unsafe#park()
方法。
如果執行到這裏就知道該線程已經被掛起了,等着被喚醒了。
接下來我們就看一下可以喚醒線程的的方法吧:
2、 countDown方法:
public void countDown() {
sync.releaseShared(1);
}
該函數也是委託其內部類完成,具體實現如下 arg爲1:
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
判斷條件tryReleaseShared:
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) { // 狀態自旋 -1
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc)) 此處使用CAS線程安全 -1 操作
return nextc == 0; // nextc 爲0時才返回真
}
}
releaseShared方法,就是說當state減1後爲0時纔會返回爲true, 執行後面的喚醒條件,否則全部忽視,假設達到喚醒條件 具體來看如何喚醒:
private void doReleaseShared() {
for (;;) {
Node h = head; //獲取頭節點,
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) { // 頭結點的狀態爲Node.SIGNAL
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h); // 這裏喚醒 很重要
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break; //這裏是否有疑問明明都有這個 Node h = head爲啥還要在判斷一次?多次一舉彆着急後面有原因
}
}
線程的共享鎖應該被無條件傳播
static final int PROPAGATE = -3;
下面執行喚醒添加
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
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); //喚醒線程
}
首先取該節點的後節點就行喚醒,如果後節點已被取消,則從最後一個開始往前找,找一個滿足添加的節點進行喚醒,
那麼看線程被喚醒後怎麼執行呢,再次看一下線程阻塞的方法:
/**
* Acquires in shared interruptible mode.
* @param arg the acquire argument
*/
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.SHARED); //該函數 用於將當前線程相關的節點將入鏈表尾部
boolean failed = true;
try {
for (;;) { //將入無限for循環
final Node p = node.predecessor(); //獲得它的前節點
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) { //唯一退出條件,也就是await()方法返回的條件非常重要!!
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的狀態值爲0】,則現在state的狀態值爲0,函數返回值爲1 ,大於0會進入其中我們繼續往下看 :
for (;;) { //將入無限for循環
final Node p = node.predecessor(); //獲得它的前節點
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) { //唯一退出條件,也就是await()方法返回的條件非常重要!!
setHeadAndPropagate(node, r); // 該方法很關鍵
p.next = null; // help GC
failed = false;
return; //到這裏返回
}
}
則進入setHeadAndPropagate(node, r); 方法的調用了。
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
setHead(node); //這裏重新設置頭節點 (已上面 第一次釋放鎖 h== head 的重複判斷相對應)
if (propagate > 0 || h == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared(); //注意這裏 會進入這裏
}
}
再次進入 doReleaseShared()方法,就是上面喚醒線程的方法。
private void doReleaseShared() {
for (;;) {
Node h = head; //獲取頭節點,
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) { // 頭結點的狀態爲Node.SIGNAL
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h); // 這裏喚醒 很重要哦
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
})
}
CountDownLatch 的喚醒機制
-
先喚醒一個頭節點 線程(第一個阻塞的線程)
-
然後被喚醒的線程重新設置頭節點然後再次進入喚醒方法中,執行喚醒線程動作。
-
如此重複下去 最終所有線程都會被喚醒,其實這也是AQS共享鎖的喚醒原理,自此完成了對countDownLatch阻塞和喚醒原理的基本分析