Java基礎--CyclicBarrier--屏障鎖(循環計數器鎖)

1. CyclicBarrier

一個同步輔助類,它允許一組線程互相等待,直到到達某個公共屏障點 (common barrier point)。在涉及一組固定大小的線程的程序中,這些線程必須不時地互相等待,此時 CyclicBarrier 很有用。因爲該 barrier 在釋放等待線程後可以重用,所以稱它爲循環 的 barrier。

舉個例子理解屏障鎖:
學校開運動會,有短跑比賽。短跑參加的人數比較多,可能需要多次比較。
首先將參與的運動員分組,比如10個一組。
每一次10開始之前都需要運動員舉手向裁判示意準備完成。
當裁判收到這10個運動員的準備示意後,裁判向發令員發出命令,可以隨時發令。

上述整個過程中,裁判做的工作就是CyclicBarrier的原理實現。

1.1 CyclicBarrier 的UML圖

在這裏插入圖片描述

2. CyclicBarrier 構造

CyclicBarrier有兩個構造方法:
在這裏插入圖片描述

2.1 CyclicBarrier(int)

在這裏插入圖片描述
根據傳入的值,創建一個parties的線程組屏障。
換句話說,就是循環計數器鎖的值是parties.

2.2 CyclicBarrier(int,Runnable)

在這裏插入圖片描述
設置線程數量是parites個,同時設置線程到達屏障點後,執行Runnable的操作。

3. CyclicBarrier 的屬性

3.1 lock

在這裏插入圖片描述
持有一個重入鎖,使用的是不公平的重入鎖。
一次只能有一個運動員向裁判示意。

3.2 trip

在這裏插入圖片描述
持有不公平的重入鎖的Condition對象。
示意準備完成的運動員需要等待。

3.3 parties

在這裏插入圖片描述
需要等待的線程數量。
一次有10個運動員比賽。

3.4 count

在這裏插入圖片描述
還未達成條件的線程數量。還需要繼續等待的線程數量。
當前還有多少個運動員沒有準備完成。

3.5 barrierCommand

在這裏插入圖片描述
線程到達屏障點後執行的操作,可空。
當所有運動員準備完成後,裁判需要向指令員發信號。
有些小型比賽,可能裁判自己發令。

3.6 generation

在這裏插入圖片描述
當前運行的線程。(還未到達屏障處)
當前正在示意的運動員。

4. Generation

在這裏插入圖片描述
是否全部等待的線程到達屏障點。默認沒有到達。
默認全部運動員都沒有準備好。

5. CyclicBarrier 的操作

5.1 await

等待全部線程到達屏障點。
在這裏插入圖片描述
直接調用dowait方法。

5.2 await(long, TimeUnit)

帶有超時時間的等待全部線程到達屏障點。
在這裏插入圖片描述
也是調用doawait方法

5.3 getNumberWaiting

獲取已經到達屏障點的線程數量
在這裏插入圖片描述
先獲取鎖,然後上鎖,獲取全部數量,獲取還需要等待的線程數量。這兩個的差就是已經到達的數量。
最後釋放鎖。

5.4 getParties

獲取屏障內線程總數
在這裏插入圖片描述

5.5 isBroken

獲取是否全部的線程到達屏障點。
在這裏插入圖片描述
先上鎖,然後獲取全部線程到達屏障點的狀態。
最後釋放鎖。

5.6 reset

重置所有信息。
在這裏插入圖片描述
先上鎖,然後喚醒已經到達屏障點的線程,重置需要等待的線程爲線程總數,最後重置是否全部線程到達屏障點的狀態。
最後釋放鎖。

5.7 nextGeneration

喚醒已經到達屏障點的全部線程,然後設置需要等待到達屏障點的線程爲線程總數,最後重置是否全部線程到達屏障點的狀態。
在這裏插入圖片描述

5.8 breakBarrier

是否異常。
設置全局的異常狀態爲true,然後設置需要等待線程數量爲線程總數,然後喚醒所有已經到達屏障點的線程。
在這裏插入圖片描述

5.9 dowait(boolean, long)

線程到達屏障處進行的操作。
判斷線程是否到達屏障處,到達屏障處做什麼操作。
運動員示意自己準備完成,應該面向裁判,看着裁判,同時舉起右手,裁判點頭後,放下右手,等待比賽開始。

	// 真正實現屏障的方法
	// await傳入false,0
    private int dowait(boolean timed, long nanos)
        throws InterruptedException, BrokenBarrierException,
               TimeoutException {
        // 獲得全局的重入鎖
        final ReentrantLock lock = this.lock;
        // 上鎖
        lock.lock();
        try {
        	// 獲取當前線程
            final Generation g = generation;
			// 如果線程已經到達屏障處,拋出異常
			// 運動員沒有向裁判舉手示意,就認爲準備好了(流程不合法)
            if (g.broken)
                throw new BrokenBarrierException();
			// 獲取線程中斷狀態,並重置中斷標誌
            if (Thread.interrupted()) {
            	// 喚醒全部已經到達屏障處的線程
            	// 因爲有線程被中斷了,也就是說,永遠不會有足夠的線程到達屏障點
            	// 一場比賽10個運動員,結果比賽的時候有一個運動員沒來,那麼這一組就無法以原有規則進行比賽
                breakBarrier();
                // 當前線程拋出中斷異常
                // 某個運動員舉手示意的時候,得到了有一個運動員沒來,那麼他也不用繼續完成示意了
                throw new InterruptedException();
            }

			// 處理的線程索引
			// 舉手示意需要從一遍到另一邊,中間不能跳過,不能重複
            int index = --count;
            // 線程索引爲0
            // 全部運動員都已經舉手示意
            if (index == 0) {  // tripped
            	// 是否需要喚醒已到達屏障點的線程
            	// 是否需要重新指定比賽(默認不需要重新制定,默認所有運動員都會來)
                boolean ranAction = false;
                try {
                	// 獲取屏障點操作
                	// 裁判示意發令員
                    final Runnable command = barrierCommand;
                    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) { // 發生中斷異常
                    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(); // 中斷當前線程
                    }
                }

				// 是否異常(這個值一直是false纔是正常的,只要索引到0,自動全部線程到達屏障點)
				// 所有的運動員示意完成
                if (g.broken) // 錯誤開始比賽
                	// 因爲index還不到0,所以還不能發令
                    throw new BrokenBarrierException();
                
                // 如果屏障點和當前線程進入的屏障點不同,那麼返回還未到達屏障點的線程個數
                if (g != generation)
                    return index;
                // 是否超時
                // 超過運動員的準備時間
                if (timed && nanos <= 0L) {
                	// 喚醒全部線程
                    breakBarrier();
                    // 拋出超時異常
                    throw new TimeoutException();
                }
            }
        } finally {
        	// 釋放鎖
            lock.unlock();
        }
    }

6. 示例程序

下面由運動員短跑比賽開始做爲一個小例子演示CyclicBarrier.

public class MyCyclicBarrier {

    public static void main(String[] args) throws InterruptedException {
        int count = 10;
        Runnable barrierAction = () -> {
            System.out.println("裁判:開始比賽--------");
        };
        CyclicBarrier cyclicBarrier = new CyclicBarrier(count, barrierAction);
        Runnable runner = () -> {
            String name;
            System.out.println("運動員" + (name = Thread.currentThread().getName()) + ":準備完成");
            try {
                Thread.sleep(300);//運動員需要300毫秒準備比賽
                cyclicBarrier.await();
                System.out.println("遠動員" + name + "開跑");
            } catch (InterruptedException e) {
                System.out.println("ie");
            } catch (BrokenBarrierException e) {
                System.out.println("比賽取消!!!!");
            }
        };
        for (int i = 0; i < 5; i++) {
            List<Thread> threads = new ArrayList<>(count);
            for (int j = 0; j < count; j++) {
                threads.add(new Thread(runner,j+""));
            }
            for (Thread thread: threads){
                thread.start(); // 運動員就位
            }
            for (Thread thread: threads){
                thread.join(); // 等待運動員開跑
            }
            cyclicBarrier.reset(); // 重置
            System.out.println(); // 分割
        }
    }

}

執行結果

運動員0:準備完成
運動員8:準備完成
運動員6:準備完成
運動員4:準備完成
運動員3:準備完成
運動員9:準備完成
運動員2:準備完成
運動員1:準備完成
運動員5:準備完成
運動員7:準備完成
裁判:開始比賽--------
遠動員1開跑
遠動員4開跑
遠動員0開跑
遠動員8開跑
遠動員3開跑
遠動員9開跑
遠動員7開跑
遠動員5開跑
遠動員6開跑
遠動員2開跑
運動員9完成
運動員2完成
運動員6完成
運動員1完成
運動員7完成
運動員0完成
運動員3完成
運動員4完成
運動員8完成
運動員5完成

運動員0:準備完成
運動員3:準備完成
......

Process finished with exit code 0

7. 總結

CyclicBarrier是一個很有用的同步工具。
它可以讓一批線程在特定的地方等待所有線程都執行到這裏,然後在繼續執行。
內部實現也很簡單,使用AQS的ConditonObject使得到達屏障點的線程等待,然後等所有線程都等待後,執行預定的操作,執行完預定的操作後,在喚醒等待的所有線程。
這個和CountDownLatch在只使用一次的時候,功能相同。但是CountDownLatch只能使用一次,而CyclicBarrier能重複使用。

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