CyclicBarrier源碼閱讀


參考:Java併發32:CyclicBarrier的基本方法和應用場景實例
這邊的描述會根據下面的例子把CyclierBarrier後面的執行稱爲發車動作.

1. 作用

        網上有一些文章描述的很清楚,這邊直接截圖查看:
在這裏插入圖片描述

2. 使用示例

        這邊的例子只是說明使用,因爲還沒閱讀到線程池所有沒有使用線程池的方式來寫,例子有一些bug,但是說明了CyclicBarrier的使用
        上一個方法描述了基本用法,下一個方法描述了reset的作用,其實她的await方法也提供了計時的功能

    public static void resetPurpose() throws InterruptedException {
        final CyclicBarrier cyclicBarrier = new CyclicBarrier(5);
        for (int i = 0; i < 5; i++) {
            new Thread() {
                @Override
                public void run() {
                    try {
                        System.out.println("等待計算");
                        Thread.sleep(3000);
                        cyclicBarrier.await();
                        System.out.println("我計算結束了");
                    } catch (InterruptedException e) {
                    } catch (BrokenBarrierException e) {
                    }
                }
            }.start();
        }

        Thread.sleep(2000);
        //重置不再支持
        cyclicBarrier.reset();
        System.out.println("不再等待");
        System.out.println("我去做別的了");
    }

}

在這裏插入圖片描述


在這裏插入圖片描述

3. 源碼閱讀

1. 參數說明

1.Generation 當前CyclierBarrier迭代的管理

        broken參數表示是否打破屏障,不再等待

    private static class Generation {
        boolean broken = false;
    }

2.lock 當前迭代管理的鎖對象

        直接可以看出這邊使用的是非公平可重入鎖

  /** The lock for guarding barrier entry */
    private final ReentrantLock lock = new ReentrantLock();

3.trip 等待發車的條件

        條件變量,後面我們會看到這邊怎麼使用這個條件變量去判斷

    /** Condition to wait on until tripped */
    private final Condition trip = lock.newCondition();

    public Condition newCondition() {
        return sync.newCondition();
    }

    final ConditionObject newCondition() {
          return new ConditionObject();
     }

4.barrierCommand 發車線程

        Runnable接口,可以不設置,但是設置之後會等待所有線程執行之後再進行執行。

    /* The command to run when tripped */
    private final Runnable barrierCommand;

5.parties 需要等待的總數

        等待發車前線程的總數量

 /** The number of parties */
    private final int parties;

6.count 剩餘等待的數量

        這個參數其實是和parties相反,用來判斷是否發車的條件,進入一個等待的線程,則這個數量-1,直到當他等於0的時候,發車

    /**
     * Number of parties still waiting. Counts down from parties to 0
     * on each generation.  It is reset to parties on each new
     * generation or when broken.
     */
    private int count;

2. 構造方法

        這邊有兩個構造方法,第一個構造方法會指明一個Runnable的接口實現,可以理解爲最終的計算線程(如果用公交車的例子就是發車動作.),當CyclicBarrier之前的線程執行完成之後,纔會執行這個總的線程

    public CyclicBarrier(int parties, Runnable barrierAction) {
        if (parties <= 0) throw new IllegalArgumentException();
        this.parties = parties;
        this.count = parties;
        this.barrierCommand = barrierAction;
    }
    public CyclicBarrier(int parties) {
        this(parties, null);
    }

3. 等待線程集滿:await

        如果不計時的話,這邊dowait方法的第一個參數傳false,表示不計時,如果需要計時則會傳入true以及時間參數.

    public int await() throws InterruptedException, BrokenBarrierException {
        try {
            return dowait(false, 0L);
        } catch (TimeoutException toe) {
            throw new Error(toe); // cannot happen
        }
    }

        這邊全局使用一個鎖,這個鎖是屬於CyclierBarrier的重入鎖,在操作線程等待的期間,需要加鎖,防止其他線程干擾到CyclierBarrier對某個線程進入等待隊列的參數設置等。
        線程每進入等待一次,則count就要做-1操作,直到count ==0的時候,如果之前聲明瞭Runnable繼承類則進行運行,重置Generation,如果count不等於0,說明還沒到達等待的期望值,則進入死循環。後面我們詳細看一下每個步驟

   private int dowait(boolean timed, long nanos) throws InterruptedException, BrokenBarrierException, TimeoutException {
        final ReentrantLock lock = this.lock;
        //進來,先獲取鎖--這個鎖是CyclicBarrier的鎖,只有一個鎖,而不是計算線程的鎖,這邊可以造成理解的誤差.
        lock.lock();
        try {
            //generation表示當前這一次CyclicBarrier的執行,如果一旦執行結束(發車了)這個參數就會被重置.表示新的開始
            final Generation g = generation;

            //如果當前線程的打破屏障的標識爲true,則拋錯,結束等待,這邊的參數只會在breakBarrier中被設置爲true,表示要打開屏障,不在等待
            if (g.broken) {
                throw new BrokenBarrierException();
            }

            //如果線程被中斷則會打破屏障並拋出異常,打破屏障這個方法我們等下來看
            if (Thread.interrupted()) {
                breakBarrier();
                throw new InterruptedException();
            }

            //count每等待一次都會-1.直到等於0爲止
            int index = --count;
            // tripped
            //當count等於0的時候,就是準備開車的時候了
            if (index == 0) {
                boolean ranAction = false;
                try {
                    final Runnable command = barrierCommand;
                    //等於0,就開始運行發車函數了
                    if (command != null) {
                        command.run();
                    }
                    ranAction = true;
                    //這一步相當於重置,喚醒其他線程.
                    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) {
                    //發生異常如果沒有 打破標識爲false,則打破屏障
                    if (g == generation && !g.broken) {
                        breakBarrier();
                        throw ie;
                    } else {
                        // We're about to finish waiting even if we had not
                        // been interrupted, so this interrupt is deemed to
                        // "belong" to subsequent execution.
                        //否則就是中斷線程
                        Thread.currentThread().interrupt();
                    }
                }

                if (g.broken){
                    throw new BrokenBarrierException();
                }

                //這邊不等於表示重置成功了,因爲在上面的 nextGeneration()中會有重置的操作
                if (g != generation){
                    return index;
                }

                //有計時並且時間到了,就打開屏障,拋出錯誤
                if (timed && nanos <= 0L) {
                    breakBarrier();
                    throw new TimeoutException();
                }
            }
        } finally {
            //最終都應該解鎖
            lock.unlock();
        }
    }

1.當未達到指定數量時等待

        這邊分成計時和不計時的兩種情況,計時的話會通過lock提供的Condition對象調用awaitNanos方法,通過if (g != generation)判斷是否進入新的迭代,進入新的迭代則正常返回,否則超時則拋出異常.trip.await();
        這裏主要關注一下沒有計時的情況,會調用

for (; ; ) {
            try {
                //是否計時,不計時就是等待
                if (!timed) {
                    trip.await();
                } else if (nanos > 0L) {
                    //如果設置了超時,較當前時刻等待的時間數
                    nanos = trip.awaitNanos(nanos);
                }
            } catch (InterruptedException ie) {
                //發生異常如果沒有 打破標識爲false,則打破屏障
                if (g == generation && !g.broken) {
                    breakBarrier();
                    throw ie;
                } else {
                    // We're about to finish waiting even if we had not
                    // been interrupted, so this interrupt is deemed to
                    // "belong" to subsequent execution.
                    //否則就是中斷線程
                    Thread.currentThread().interrupt();
                }
            }

            if (g.broken){
                throw new BrokenBarrierException();
            }

            //這邊不等於表示重置成功了,因爲在上面的 nextGeneration()中會有重置的操作
            if (g != generation){
                return index;
            }

            //有計時並且時間到了,就打開屏障,拋出錯誤
            if (timed && nanos <= 0L) {
                breakBarrier();
                throw new TimeoutException();
            }
        }

1.加入條件隊列,休眠等待

        我們要分析的是await這個方法,這個trip是ReentrantLock提供的ConditionObject類.

        final ConditionObject newCondition() {
            return new ConditionObject();
        }
		try {
                  //是否計時,不計時就是等待
                  if (!timed) {
                      trip.await();
                  } else if (nanos > 0L) {
                      //如果設置了超時,較當前時刻等待的時間數
                      nanos = trip.awaitNanos(nanos);
                  }
              }

        await方法會把節點加入到條件隊列並且釋放同步隊列的節點,這邊有一個步驟是park,就是把節點休眠,等待被喚醒
        當節點被喚醒的時候,會判斷中斷標識和是否在同步隊列中(如果已經被signalAll添加到隊列中),則繼續往下,通過隊列爭取鎖,並通過中斷狀態判斷應該報錯還是設置中斷標識結束整個過程.

        @Override
        public final void await() throws InterruptedException {
            //中斷狀態報錯
            if (Thread.interrupted()) {
                throw new InterruptedException();
            }
            //加入到條件等待隊列,設置waitStatus爲CONDITION
            Node node = addConditionWaiter();
            //這邊因爲上面的線程lock了,所以除非異常情況,否則正常是release成功,從同步隊列中解放
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            //對不在同步隊列的節點進行park(休眠)
            while (!isOnSyncQueue(node)) {
                LockSupport.park(this);
                //這邊還是會判斷一下中斷標識,就是中斷是在signal之前還是在sinal之後,如果沒有被中斷就是返回0,會一直等待節點被添加到同步隊列結束循環.
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) {
                    break;
                }
            }
            //這邊應該應該是最後一個線程進來把count置爲0,然後進行signalAll操作(condition隊列轉同步隊列),進行鎖的獲取和線程執行
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE) {
                interruptMode = REINTERRUPT;
            }
            // clean up if cancelled
            //釋放被cancel的節點
            if (node.nextWaiter != null) {
                unlinkCancelledWaiters();
            }
            //判斷是否發生中斷
            if (interruptMode != 0) {
                reportInterruptAfterWait(interruptMode);
            }
        }

2.當數量達到時打開屏障

        當count == 0的時候,會運行我們之前設置的Runnable接口實現類,如果不爲空的話,否則會開始新的迭代nextGeneration(),這個方法會重置一些參數,並且喚醒其他線程.

	if (index == 0) {
             boolean ranAction = false;
             try {
                 final Runnable command = barrierCommand;
                 //等於0,就開始運行發車函數了
                 if (command != null) {
                     command.run();
                 }
                 ranAction = true;
                 //這一步相當於重置,喚醒其他線程.
                 nextGeneration();
                 return 0;
             } finally {
                 //如果這邊發生異常就會打破屏障
                 if (!ranAction) {
                     breakBarrier();
                 }
             }
         }
    private void nextGeneration() {
        // signal completion of last generation
        //喚醒所有的線程,執行後面的操作
        trip.signalAll();
        // set up next generation
        //重置當前迭代的參數
        count = parties;
        generation = new Generation();
    }

1.喚醒所有線程

        循環每一個條件隊列的節點(從first開始),每一個節點進行doSignalAll進行喚醒操作。

        //將此條件的所有線程從等待隊列移動到所屬鎖的等待隊列。
        public final void signalAll() {
            //持鎖線程不等於當前線程
            if (!isHeldExclusively()) {
                throw new IllegalMonitorStateException();
            }
            //拿到第一個等待的節點
            Node first = firstWaiter;
            //如果第一個節點不爲空,通過第一個節點喚醒
            if (first != null) {
                doSignalAll(first);
            }
        }

        doSignalAll會把條件隊列中的節點轉換到同步隊列,這也是我們上面的await退出休眠之後,去爭取隊列獲取鎖操作的來源,他加入同步隊列是在這邊進行操作的。

        //移除所有節點從條件隊列到同步隊列
        private void doSignalAll(Node first) {
            //置空第一個最後一個節點
            lastWaiter = firstWaiter = null;
            //循環直到遍歷到第一個不爲null的節點
            do {
                Node next = first.nextWaiter;
                first.nextWaiter = null;
                //這個就是轉換的操作,從第一個非空節點開始
                transferForSignal(first);
                first = next;
            } while (first != null);
        }

        這邊判斷沒有辦法從CONDITION通過CAS方式置爲0的原因是waitStates被改變了,改變的地方就是把這個節點cancel了,所以要把它淘汰掉。
        再往後通過enq死循環把每個節點添加到尾部
        如果狀態沒有被設置爲SIGNAL或者waitStates>0(節點被取消)。這個時候會喚醒該節點,一個目的是讓他執行把waitStates改變成SIGNAL,進行同步隊列的執行操作,一個也是喚醒它可能進行節點淘汰操作。

    final boolean transferForSignal(Node node) {
        //如果不是CONDITION狀態這個CAS操作絕對失敗的,所以被cancel的節點跳過,繼續往下一個非空節點走
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
            return false;
        }
        //加入隊列
        Node p = enq(node);
        int ws = p.waitStatus;
        //被取消的節點或者是無法設置SIGNAL的節點通過喚醒重新同步.
        //這邊我的想法是喚醒是爲了清除cancel的節點,或者是還沒有重置爲SIGNAL的節點,有兩個想法在裏面
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL)) {
            LockSupport.unpark(node.thread);
        }
        return true;
    }

3.打破屏障:breakBarrier

        這邊有好幾個地方會調用breakBarrier方法,就是打破屏障,包括我們後面看的reset方法。這個方法主要的目的一個是設置broke參數,一個是喚醒所有的條件隊列線程節點。

    private void breakBarrier() {
        generation.broken = true;
        count = parties;
        trip.signalAll();
    }

4.圖解

        看圖應該能更好的理解上面的過程
在這裏插入圖片描述

4. reset

        reset可以重置強行釋放屏障,這邊的兩個函數我們上面也講了就不多贅述了。

    public void reset() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            breakBarrier();   // break the current generation
            nextGeneration(); // start a new generation
        } finally {
            lock.unlock();
        }
    }

發佈了84 篇原創文章 · 獲贊 15 · 訪問量 3217
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章