源碼分析AbstractQuenedSynchronized(三)

第一篇:源碼分析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十分輕鬆。


總結

  1. CountdownLatch和Semaphore的實現思路很相似,內部都有一個AQS的實現類Sync,都是將線程加入到阻塞隊列中掛起,前者是多個線程調用countdown方法用於state遞減,state減爲0之前調用await方法的線程都加入到阻塞隊列中掛起,當state減爲0後阻塞隊列中的線程被喚醒;後者是線程調用acquire方法將state遞減,state減爲0之前這些線程都能獲得資源,state減爲0後將線程都添加到阻塞隊列中掛起,調用release方法增加state使其大於0後嘗試去喚醒阻塞隊列中的線程。
  2. CyclicBarrier主要通過Condition的實現類ConditionObject來實現,內部有個類Generation用來維護當前代的柵欄是否已被打破,在count(還沒有達到屏障的線程數)減爲0之前達到線程的屏障都會調用condition的await方法加入到條件隊列中掛起等待,當count==0後,說明所有線程都已達到屏障,這時候就會釋放屏障,喚醒所有等待隊列中的結點,它們轉移到阻塞隊列中重新嘗試去獲得鎖繼續執行

參考:https://javadoop.com/post/AbstractQueuedSynchronizer

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章