第一篇:源碼分析AbstractQuenedSynchronized(一)
第二篇:源碼分析AbstractQuenedSynchronized(二)
文章目錄
本文主要結合AQS分析JDK併發包中的幾個非常有用的併發工具類:CountDownLatch、CyclicBarrier和Semaphore.
CountDownLatch
CountDownLatch允許一個或多個線程等待其他線程完成操作。
其內部依賴一個AQS的實現類Sync實現功能,如下圖所示
使用示例
import java.util.concurrent.CountDownLatch;
public class CountDownLatch_Test {
static CountDownLatch t3= new CountDownLatch(2);
public static void main(String[] args) throws InterruptedException {
Thread work1 = new Thread(new Runnable() {
public void run() {
System.out.println("任務1處理結束");
t3.countDown();
}
});
Thread work2 = new Thread(new Runnable() {
public void run() {
System.out.println("任務2處理結束");
t3.countDown();
}
});
work1.start();
work2.start();
t3.await();//阻塞,直到count減爲0
System.out.println("兩個任務處理結束!");
}
}
圖片源於https://javadoop.com/post/AbstractQueuedSynchronizer-3
構造函數
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);//count實際被設置爲同步狀態的state值
}
private final Sync sync;
/**
* 使用內部類Sync來實現功能
*/
private static final class Sync extends AbstractQueuedSynchronizer {
//構造函數:將state設置爲count值
Sync(int count) {
setState(count);
}
//該方法用於判斷state是否爲0
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
//用在countDown方法中,將state減1,然後判斷state是否爲0
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的重點其實就是await和countDown 兩個方法,我們先分析await方法,看看線程是如何被掛起的
await方法(進入該方法到線程掛起)
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
/*====================AQS:acquireSharedInterruptibly==============================
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
//檢查到線程中斷,則直接拋出異常
if (Thread.interrupted())
throw new InterruptedException();
//state!=0,則直接從await方法返回,否則進入doAcquireSharedInterruptibly方法
if (tryAcquireShared(arg) < 0)//return (state == 0) ? 1 : -1;
doAcquireSharedInterruptibly(arg);
}
=====================================================================================*/
/*====================AQS:doAcquireSharedInterruptibly==========================
private void doAcquireSharedInterruptibly(int arg) throws InterruptedException {
//當前線程node加入阻塞隊列末尾,狀態爲SHARED,由此我們看出調用await方法的線程一開始都會被加入阻塞隊列中
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
//只要state不等於0,這個值返回-1
int r = tryAcquireShared(arg);//return (state == 0) ? 1 : -1;
//第一次進入await方法時,r應該是爲-1的,這個if代碼塊主要是線程被喚醒後的後續處理過程,等線程被喚醒後我們再回頭看裏面的邏輯
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
//調用await方法,正常情況下第一次走這裏的邏輯,在這裏面線程會被掛起,等待喚醒
if (shouldParkAfterFailedAcquire(p, node) &&//該方法爲AQS方法,主要將node的前驅結點p設爲SINGNAL狀態
parkAndCheckInterrupt())//該方法爲AQS方法,利用LockSupport工具掛起當前線程,同時返回線程的中斷狀態
throw new InterruptedException();//如果進入這個邏輯,則說明線程中斷了,拋出中斷異常
}
} finally {
if (failed)
cancelAcquire(node);
}
}
=======================================================================================*/
在shouldParkAfterFailedAcquire方法中線程被掛起後,我們接下來就可以看線程是如何被喚醒的,即分析countDown方法
countDown方法
public void countDown() {
sync.releaseShared(1);
}
/*=============AQS的releaseShared方法=========================
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {//將state減1,然後判斷state是否爲0
//state爲0,準備喚醒線程
doReleaseShared();
return true;
}
//state不爲0,直接返回
return false;
}
================================================================== */
/*=============AQS的doReleaseShared方法,state==0時纔會進入該方法=========================
private void doReleaseShared() {
for (;;) {
Node h = head;
//h爲null說明阻塞隊列爲空,h=tail說明head後面沒有結點在排隊,即阻塞隊列的結點都被喚醒了,因此這兩種情況不需要繼續喚醒後繼結點了
if (h != null && h != tail) {
int ws = h.waitStatus;//還記得分析await時的shouldParkAfterFailedAcquire方法嗎?
//等待線程t3入隊的時候已經將它的前繼結點的waitStatus設爲SINGAL(-1)了,所以head的狀態爲SINGAL
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // CAS失敗則重新循環
unparkSuccessor(h);//喚醒head的後繼結點t3
}
else if (ws == 0 &&!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
================================================================== */
await方法(state==0後被喚醒)
//回顧await方法(進入該方法到線程掛起),調用await方法的線程被喚醒後會進入這個if邏輯
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
/*=============AQS的setHeadAndPropagate方法,node爲head的後繼結點,propagate=1=========================
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
//將即將要被喚醒的結點(head的後繼結點)設爲頭結點
setHead(node);
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
//可以看出,阻塞隊列的結點被喚醒後還會繼續調用doReleaseShared方法喚醒隊列中後面的結點
//調用doReleaseShared方法大概的過程是這樣的:state減爲0後,
//第一次從countdown方法中調用該方法會喚醒阻塞隊列中的第一個結點,喚醒後的結點還會繼續調用該方法去喚醒後面的結點
doReleaseShared();
}
}
================================================================== */
/*=============AQS的doReleaseShared方法,state==0時纔會進入該方法=========================
private void doReleaseShared() {
for (;;) {
Node h = head;
//h爲null說明阻塞隊列爲空,h=tail說明head後面沒有結點在排隊,即阻塞隊列的結點都被喚醒了,因此這兩種情況不需要繼續喚醒後繼結點了
if (h != null && h != tail) {
int ws = h.waitStatus;//還記得分析await時的shouldParkAfterFailedAcquire方法嗎?
//等待線程t3入隊的時候已經將它的前繼結點的waitStatus設爲SINGAL(-1)了,所以head的狀態爲SINGAL
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // 循環直到將head狀態設爲0
unparkSuccessor(h);//喚醒head的後繼結點t3
}
else if (ws == 0 &&//這個CAS失敗的場景是:執行到這裏,剛好有一個結點入隊,入隊會將這個ws設爲-1
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head)
//這是多次調用doReleaseShared方法後最終結束的地方,當最後一次調用doReleaseShared進入到這裏時說明阻塞隊列的所有結點都被喚醒,head後面沒有結點了
break;
}
}
================================================================== */
CyclicBarrier
CyclicBarrier的字母意思是可循環使用的屏障。它的功能是:讓一組線程到達一個屏障時被阻塞,直到最後一個線程到達屏障時,打開屏障,所有在柵欄上的線程纔會繼續運行。
圖片源於https://javadoop.com/post/AbstractQueuedSynchronizer-3
package java.util.concurrent;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class CyclicBarrier {
//CyclicBarrier與CountDownLatch相比可以重複使用,因此具有代的概念
private static class Generation {
boolean broken = false;
}
/** The lock for guarding barrier entry */
private final ReentrantLock lock = new ReentrantLock();
/** Condition to wait on until tripped */
private final Condition trip = lock.newCondition();
//屏障攔截的線程數量
private final int parties ;
//越過屏幕前優先執行的動作
private final Runnable barrierCommand;
//當前所屬的代
private Generation generation = new Generation();
//還沒到達屏障的線程,從parties遞減到0
private int count;
/**
* 開啓新的一代
*/
private void nextGeneration() {
// 喚醒在柵欄上等待的所有線程
trip.signalAll();
// 建立新的一代
count = parties;
generation = new Generation();
}
/**
* 打破柵欄
*/
private void breakBarrier() {
//設置標誌位
generation.broken = true;
//重置count,喚醒柵欄上所有等待的線程
count = parties;
trip.signalAll();
}
/**
* Main barrier code, covering the various policies.
*/
private int dowait(boolean timed, long nanos)//timed:是否帶有超時機制
throws InterruptedException, BrokenBarrierException,
TimeoutException {
final ReentrantLock lock = this.lock;
//該方法中會執行Condition的await方法,因此首先肯定要獲得鎖
lock.lock();
try {
final Generation g = generation;
//柵欄被打破,拋出異常
if (g.broken)
throw new BrokenBarrierException();
//線程中斷,拋出異常
if (Thread.interrupted()) {
breakBarrier();
throw new InterruptedException();
}
int index = --count;//調用一次await方法,count減1
//count==0,準備讓柵欄上線程通過
if (index == 0) { // tripped
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
//如果設置了barrierCommand則優先執行barrierCommand
if (command != null)
command.run();
ranAction = true;//設置標誌位,說明command.run()沒有發生異常
nextGeneration();//最後一個線程到達屏障後會喚醒所有線程並建立新的一代
return 0;
} finally {
if (!ranAction)
//如果執行指定操作的時候發生了異常,則需要打破柵欄
breakBarrier();
}
}
// loop until tripped, broken, interrupted, or timed out
for (;;) {
try {
if (!timed)
//沒有超時機制
trip.await();//釋放鎖
else if (nanos > 0L)
nanos = trip.awaitNanos(nanos);
} catch (InterruptedException ie) {
//邏輯都這裏,等待的線程在Condition的await方法中被中斷
if (g == generation && ! g.broken) {
//仍然在當前代,之前柵欄也沒有被打破過
//則,打破柵欄並拋出異常
breakBarrier();
throw ie;
} else {
//邏輯走到這裏,說明新的一代已經產生了,即最後一個線程await執行完成,因此沒有必要拋出中斷異常,記錄中斷信息即可
Thread.currentThread().interrupt();
}
}
//醒來發現柵欄已經被打破,拋出異常
if (g.broken)
throw new BrokenBarrierException();
// 這個 for 循環除了異常,就是要從這裏退出了
// 我們要清楚,最後一個線程在執行完指定任務(如果有的話),會調用 nextGeneration 來開啓一個新的代
// 然後釋放掉鎖,其他線程從 Condition 的 await 方法中得到鎖並返回,然後到這裏的時候,其實就會滿足 g != generation 的
// 那什麼時候不滿足呢?barrierCommand 執行過程中拋出了異常,那麼會執行打破柵欄操作,
// 設置 broken 爲true,然後喚醒這些線程。這些線程會從上面的 if (g.broken) 這個分支拋 BrokenBarrierException 異常返回
// 當然,還有最後一種可能,那就是 await 超時,此種情況不會從上面的 if 分支異常返回,也不會從這裏返回,會執行後面的代碼
if (g != generation)
return index;
//醒來發現超時,打破柵欄,拋出異常
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
}
} finally {
lock.unlock();
}
}
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;
this.count = parties;//count初始爲parties
this.barrierCommand = barrierAction;
}
public CyclicBarrier(int parties) {
this(parties, null);
}
public int getParties() {
return parties;
}
//不帶超時機制
public int await() throws InterruptedException, BrokenBarrierException {
try {
return dowait(false, 0L);
} catch (TimeoutException toe) {
throw new Error(toe); // cannot happen
}
}
//帶超時機制,會拋出異常
public int await(long timeout, TimeUnit unit)
throws InterruptedException,
BrokenBarrierException,
TimeoutException {
return dowait(true, unit.toNanos(timeout));
}
//直接通過broken的值來判斷柵欄是否被打破
/**
* 有以下幾種情況,柵欄會被打破
* 1.線程中斷
* 2.超時
* 3.執行指定barrierCommand操作時發生了異常
*/
public boolean isBroken() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return generation.broken;
} finally {
lock.unlock();
}
}
public void reset() {
final ReentrantLock lock = this.lock;
//打破柵欄的過程中有一個步驟是喚醒等待線程,而signalAll操作是需要獲得鎖的,因此會進行加鎖操作
lock.lock();
try {
breakBarrier(); // break the current generation
nextGeneration(); // start a new generation
} finally {
lock.unlock();
}
}
//獲得在柵欄上等待的線程數
public int getNumberWaiting() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return parties - count;
} finally {
lock.unlock();
}
}
}
Semaphore
Semaphore(信號量)是用來控制同時訪問特定資源的線程數量,它通過協調各個線程以保證合理的使用公共資源。
使用示例
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
public class SemaphoreTest {
private static final int THREAD_COUNT=30;
private static ExecutorService threadPool= Executors.newFixedThreadPool(THREAD_COUNT);
private static Semaphore semaphore=new Semaphore(10);//只允許10個線程同時併發
public static void main(String[] args) {
for (int i = 0; i <THREAD_COUNT; i++) {
threadPool.execute(new Runnable() {
public void run() {
try {
semaphore.acquire();//獲取許可證
System.out.println("save data");
semaphore.release();//歸還許可證
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
threadPool.shutdown();
}
}
Semaphore的內部結構和ReentrantLock很相似,內部都有一個AQS的實現類Sync,Sync又有兩個實現類,用於實現兩種公平策略。
構造函數
//默認非公平鎖
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
//可通過構造參數指定公平策略
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
acquire方法
//下面四種方法主要區分在獲取指定數量的資源以及是否響應中斷
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public void acquireUninterruptibly() {
sync.acquireShared(1);
}
public void acquire(int permits) throws InterruptedException {
if (permits < 0) throw new IllegalArgumentException();
sync.acquireSharedInterruptibly(permits);
}
public void acquireUninterruptibly(int permits) {
if (permits < 0) throw new IllegalArgumentException();
sync.acquireShared(permits);
}
首先來看我們常用的acquire()方法,它的功能是響應中斷式的獲取一個資源(許可證)
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
/*=============AQS類的acquireSharedInterruptibly方法============
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
//檢查到線程中斷,則直接拋出異常
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
//當state已經爲0時即資源爲空時,remaining<0,此時線程準備進入阻塞隊列
doAcquireSharedInterruptibly(arg);
}
==================================================================*/
由於Sync有兩個實現類,對應兩種公平策略,它們都重寫了tryAcquireShared(arg)方法,下面進行對比:
//==================非公平策略NonfairSync=======================
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
//=================公平策略FairSync============================
protected int tryAcquireShared(int acquires) {
for (;;) {
//和非公平策略相比區別:首先會判斷是否有線程在阻塞隊列中排隊,即head結點後面有結點在排隊
if (hasQueuedPredecessors())
return -1;
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
}
當state爲0後,再調用acquire方法就會進入下面的邏輯,線程加入到阻塞隊列中,這個方法在CountdownLatch中介紹過,這裏就不贅述了
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
//當前線程node加入阻塞隊列末尾
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
//只要state不等於0,這個值返回-1
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
//parkAndCheckInterrupt方法中線程掛起
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
線程掛起後要等待其他線程調用realease方法釋放資源,才能重新獲得資源繼續執行,下面看release方法
release方法
public void release() {
sync.releaseShared(1);
}
public void release(int permits) {
if (permits < 0) throw new IllegalArgumentException();
sync.releaseShared(permits);
}
/*===================AQS:releaseShared=======================
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
=================================================================*/
//方法很簡單,釋放資源,state增加
protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases;
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
return true;
}
}
/*這個方法在Countdownlatch中也介紹過,主要作用是喚醒阻塞隊列中的結點
===================AQS:doReleaseShared=============================
private void doReleaseShared() {
for (;;) {
Node h = head;
//h爲null說明阻塞隊列爲空,h=tail說明阻塞隊列的結點都被喚醒了,因此這兩種情況不需要繼續喚醒後繼結點了
if (h != null && h != tail) {
int ws = h.waitStatus;//head結點後面的結點在入隊的時候已經將頭結點的waitStatus設爲SINGAL(-1)了
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // 循環直到將head狀態設爲0
unparkSuccessor(h);//喚醒head的後繼結點
}
else if (ws == 0 &&!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
//說明
if (h == head) // loop if head changed
break;
}
}
====================================================================*/
在搞懂CountdownLatch的基礎上,分析Semaphore十分輕鬆。
總結
- CountdownLatch和Semaphore的實現思路很相似,內部都有一個AQS的實現類Sync,都是將線程加入到阻塞隊列中掛起,前者是多個線程調用countdown方法用於state遞減,state減爲0之前調用await方法的線程都加入到阻塞隊列中掛起,當state減爲0後阻塞隊列中的線程被喚醒;後者是線程調用acquire方法將state遞減,state減爲0之前這些線程都能獲得資源,state減爲0後將線程都添加到阻塞隊列中掛起,調用release方法增加state使其大於0後嘗試去喚醒阻塞隊列中的線程。
- CyclicBarrier主要通過Condition的實現類ConditionObject來實現,內部有個類Generation用來維護當前代的柵欄是否已被打破,在count(還沒有達到屏障的線程數)減爲0之前達到線程的屏障都會調用condition的await方法加入到條件隊列中掛起等待,當count==0後,說明所有線程都已達到屏障,這時候就會釋放屏障,喚醒所有等待隊列中的結點,它們轉移到阻塞隊列中重新嘗試去獲得鎖繼續執行