1. 程式人生 > >最常用的CountDownLatch, CyclicBarrier你知道多少? (Java工程師必會)

最常用的CountDownLatch, CyclicBarrier你知道多少? (Java工程師必會)

CountdownLatch,CyclicBarrier是非常常用併發工具類,可以說是Java工程師必會技能了。不但在專案實戰中經常涉及,而且在編寫壓測程式,多執行緒demo也是必不可少,所以掌握它們的用法和實現原理非常有必要。

念念不忘,必有迴響!
點贊走一走,找到女朋友~

等待多執行緒完成的CountDownLatch

CountDownLatch允許一個或多個執行緒等待其他執行緒完成操作。也就是說通過使用CountDownLatch工具類,可以讓一組執行緒等待彼此執行完畢後在共同執行下一個操作。具體流程如下圖所示,箭頭表示任務,矩形表示柵欄,當三個任務都到達柵欄時,柵欄後wait的任務才開始執行。

CountDownLatch維護有個int型的狀態碼,每次呼叫countDown時狀態值就會減1;呼叫wait方法的執行緒會阻塞,直到狀態碼為0時才會繼續執行。

在多執行緒協同工作時,可能需要等待其他執行緒執行完畢之後,主執行緒才接著往下執行。首先我們可能會想到使用執行緒的join方法(呼叫join方法的執行緒優先執行,該執行緒執行完畢後才會執行其他執行緒),顯然這是可以完成的。

使用Thread.join()方法實現

public class RunningRaceTest {
    public static void main(String[] args) throws InterruptedException {
        Thread runner1 = new Thread(new Runner(), "1號");
        Thread runner2 = new Thread(new Runner(), "2號");
        Thread runner3 = new Thread(new Runner(), "3號");
        Thread runner4 = new Thread(new Runner(), "4號");
        Thread runner5 = new Thread(new Runner(), "5號");
        runner1.start();
        runner2.start();
        runner3.start();
        runner4.start();
        runner5.start();

        runner1.join();
        runner2.join();
        runner3.join();
        runner4.join();
        runner5.join();

        // 裁判等待5名選手準備完畢
        System.out.println("裁判:比賽開始~~");
    }
}

class Runner implements Runnable {
    @Override
    public void run() {
        try {
            int sleepMills = ThreadLocalRandom.current().nextInt(1000);
            Thread.sleep(sleepMills);
            System.out.println(Thread.currentThread().getName() + " 選手已就位, 準備共用時: " + sleepMills + "ms");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Thread.join()完全可以實現這個需求,不過存在一個問題,如果呼叫join的執行緒一直存活,則當前執行緒則需要一直等待。這顯然不夠靈活,並且當前執行緒可能會出現死等的情況。

更加靈活的CountDownLatch

jdk1.5之後的併發包中提供了CountDownLatch併發工具了,也可以實現join的功能,並且功能更加強大。

// 參賽選手執行緒
class Runner implements Runnable {
    private CountDownLatch countdownLatch;
    
    public Runner(CountDownLatch countdownLatch) {
        this.countdownLatch = countdownLatch;
    }

    @Override
    public void run() {
        try {
            int sleepMills = ThreadLocalRandom.current().nextInt(1000);
            Thread.sleep(sleepMills);
            System.out.println(Thread.currentThread().getName() + " 選手已就位, 準備共用時: " + sleepMills + "ms");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 準備完畢,舉手示意
            countdownLatch.countDown();
        }
    }
}

public class RunningRaceTest {
    public static void main(String[] args) throws InterruptedException {
        // 使用執行緒池的正確姿勢
        int size = 5;
        AtomicInteger counter = new AtomicInteger();
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(size, size, 1000, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100), (r) -> new Thread(r, counter.addAndGet(1) + " 號 "), new ThreadPoolExecutor.AbortPolicy());
        
        CountDownLatch countDownLatch = new CountDownLatch(5);
        for (int i = 0; i < size; i++) {
            threadPoolExecutor.submit(new Runner(countDownLatch));
        }

        // 裁判等待5名選手準備完畢
        countDownLatch.await(); // 為了避免死等,也可以新增超時時間
        System.out.println("裁判:比賽開始~~");

        threadPoolExecutor.shutdownNow();
    }
}

輸出結果:

5 號  選手已就位, 準備共用時: 20ms
4 號  選手已就位, 準備共用時: 156ms
1 號  選手已就位, 準備共用時: 288ms
2 號  選手已就位, 準備共用時: 519ms
3 號  選手已就位, 準備共用時: 945ms
比賽開始~~

同步屏障CyclicBarrier

CyclicBarrier可以實現CountDownLatch一樣的功能,不同的是CountDownLatch屬於一次性物件,聲明後只能使用一次,而CyclicBarrier可以迴圈使用。

從字面意義上來看,CyclicBarrier表示迴圈的屏障,當一組執行緒全部都到達屏障時,屏障才會被移除,否則只能阻塞在屏障處。

public class RunningRace {
    public static void main(String[] args) {
        // 使用執行緒池的正確姿勢
        int size = 5;
        AtomicInteger counter = new AtomicInteger();
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(size, size, 1000, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100), (r) -> new Thread(r, counter.addAndGet(1) + " 號 "), new ThreadPoolExecutor.AbortPolicy());

        CyclicBarrier cyclicBarrier = new CyclicBarrier(5, () -> System.out.println("裁判:比賽開始~~"));
        for (int i = 0; i < 10; i++) {
            threadPoolExecutor.submit(new Runner(cyclicBarrier));
        }
    }
}

class Runner implements Runnable {
    private CyclicBarrier cyclicBarrier;

    public Runner(CyclicBarrier countdownLatch) {
        this.cyclicBarrier = countdownLatch;
    }

    @Override
    public void run() {
        try {
            int sleepMills = ThreadLocalRandom.current().nextInt(1000);
            Thread.sleep(sleepMills);
            System.out.println(Thread.currentThread().getName() + " 選手已就位, 準備共用時: " + sleepMills + "ms" + cyclicBarrier.getNumberWaiting());
            cyclicBarrier.await();
        } catch (InterruptedException | BrokenBarrierException e) {
            e.printStackTrace();
        }
    }
}

由於CyclicBarrier可以迴圈使用,所以CyclicBarrier的構造方法中可以傳入一個Runnable引數,在每一輪執行完畢之後就會立刻執行這個Runnable任務。

CountDownLatch設計與實現

CountDownLath是基於AQS框架的一種簡單實現,有兩個核心的方法,即await()和countDown(),通過構造方法傳入一個狀態值,呼叫await()方法時執行緒會阻塞,直到狀態碼被修改成0時才會返回,每次呼叫countDown()時會將狀態值減1。

wait方法:執行wait方法後,會嘗試獲取同步狀態,如果為狀態為0則方法繼續執行,否擇當前執行緒會被加入到同步佇列中,詳情可見筆者關於AQS的兩篇文章。

public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg)
    throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    // 如果狀態碼不為0,嘗試獲取同步狀態,如果失敗則被加入到同步佇列中
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
}
// 當狀態碼為0時返回1,否擇返回-1,這個方法中引數沒有任何用處
protected int tryAcquireShared(int acquires) {
    return (getState() == 0) ? 1 : -1;
}

countDown方法:每次執行countDown方法時,會將狀態碼的值減1.

public void countDown() {
    sync.releaseShared(1);
}

CyclicBarrier的設計與實現

CyclicBarrier與CountDownLatch實現思想相同,也是基於AQS框架實現。不同的是CyclicBarrier內部維護一個狀態值, 藉助基於AQS實現的鎖ReentrantLock來實現狀態值的同步更新,以及AQS除了同步狀態之外的另一個核心概念條件佇列來完成執行緒的阻塞。

parties: 和CountdownLatch中的狀態值一樣,用來記錄每次要相互等待的執行緒數量,只有parties個執行緒同時到達屏障時,才會喚醒阻塞的執行緒。

count臨時計數器: 由於CyclicBarrier是可以迴圈使用的,count可以理解為是一個臨時變數,每一輪執行完畢或者被打斷都會重置count為parties值。

Generation內部類: 只有一個屬性 broken表示當前這一輪執行是否被中斷,如果被中斷後其他執行緒再執行await方法會丟擲異常(目的是停止本輪執行緒未執行執行緒的繼續執行)。

await方法: 當執行await方法時,會同步得對內部的count執行--count操作, 如果count = 0,則執行barrierCommand任務(通過構造方法傳來的Runnable引數)。

reset方法:中斷本輪執行,重置count值,喚醒等待的執行緒然後開始下一輪,此時本輪正在執行的執行緒呼叫await方法會丟擲異常。

// await方法實際執行的程式碼
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()) {
            breakBarrier();
            throw new InterruptedException();
        }
        // 本輪執行的計數器 數值-1
        int index = --count;
        if (index == 0) {  // 計數器值=1, 本輪執行緒全部到達屏障,執行barrierCommand任務
            boolean ranAction = false;
            try {
                final Runnable command = barrierCommand;
                if (command != null)
                    command.run();
                ranAction = true;
                nextGeneration();// 喚醒所有等待在條件佇列上的任務
                return 0;
            } finally {
                if (!ranAction)
                    breakBarrier();
            }
        }

        // 如果狀態不等於0,迴圈等待直到計數器值為0,本輪執行被打破,執行緒被中斷,或者等待超時
        for (;;) {
            try {
                if (!timed)
                    // 狀態碼不為0,將當前執行緒加入到條件佇列中,進入阻塞狀態
                    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();
                }
            }

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

            if (g != generation)
                return index;

            if (timed && nanos <= 0L) {
                breakBarrier();// 喚醒所有條件佇列中的執行緒,重置count的值
                throw new TimeoutException();
            }
        }
    } finally {
        lock.unlock();
    }
}

重置柵欄的狀態

public void reset() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        breakBarrier();   // break the current generation
        nextGeneration(); // start a new generation
    } finally {
        lock.unlock();
    }
}
/**
 * Sets current barrier generation as broken and wakes up everyone.
 * Called only while holding lock.
 */
private void breakBarrier() {
    generation.broken = true;
    count = parties;
    trip.signalAll();
}

當一輪執行完畢之後,既count=0後,CyclicBarrier的臨時狀態會重置為parties

/**
 * 進入下一輪
 * 喚醒所有等待執行緒,充值count
 */
private void nextGeneration() {
    // signal completion of last generation
    trip.signalAll();
    // set up next generation
    count = parties;
    generation = new Generation();
}

總結

  1. CountDownLatch建立後只能使用一次,而CyclicBarrier可以迴圈使用,並且CyclicBarrier功能更完善。
  2. CountDownLatch內部的狀態是基於AQS中的狀態資訊,而CyclicBarrier中的狀態值是單獨維護的,使用ReentrantLock加鎖保證併發修改狀態值的資料一致性。
  3. 它們的使用場景:允許一個或多個執行緒等待其他執行緒完成操作, 即當指定數量執行緒執行完某個操作再繼續執行下一個操作。

相關推薦

常用CountDownLatch, CyclicBarrier知道多少? (Java工程師)

CountdownLatch,CyclicBarrier是非常常用併發工具類,可以說是Java工程師必會技能了。不但在專案實戰中經常涉及,而且在編寫壓測程式,多執行緒demo也是必不可少,所以掌握它們的用法和實現原理非常有必要。 念念不忘,必有迴響! 點贊走一走,找到女朋友~ 等待多執行緒完成的Count

知道Java的四種引用類型嗎

級別 block 隊列 image stringbu ror new ace 你知道 從大一自學Java已經兩年了,自覺已經可以獨當一面,(其實遠遠不足),最近一直在看書。關於java四種引用類型,我也是剛了解,特此記下! 在Java中提供了四個級別的引用:強引用,軟引

知道JAVA程式設計師和C程式設計師的差別嗎

知道JAVA程式設計師和C程式設計師的差別嗎?食堂裡,吃完飯就走的是JAVA程式設計師,吃完飯還要自己收拾的那就是是C程式設計師。至於為什麼會這樣,大家都明白(因為JAVA自帶垃圾回收機制,C需要手動釋放記憶體)←這就是原因。 我是個程式猿,一天我坐在路邊一邊喝水一邊苦苦

常用Linux 但是知道它和Unix區別嗎

  有很多初學Linux的人比較關心linux和windows的區別,這裡還有一點就是Linux Unix的區別,弄清楚一些區別有助於我們對作業系統的瞭解.這裡敘述Linux Unix的區別.    Linux和UNIX的最大的區別是,前者是開發原始碼的自由軟體,而後者是對原始碼實行智慧財

知道Facebook工程師是如何高效工作的嗎?

編者按:Facebook 的工程師有哪些高效工作的經驗呢?軟體工程師訪談了多位 Facebook 的高產工程師,總結了他們的共同經驗以及晉級之路,供各位參考。 成為高效開發者這件事你可以通過經驗、書本、或者試驗和錯誤來學習。但成為高效開發者的最有效方式之一是直接向高效開發者學習。我訪談了 Fac

Python愛好者得看11個常用站點!知道幾個?

學習一門程式語言,除了語法,最重要的是學習解決問題。很多時候單憑自己的能力確實無法做到完美解決,所以無論是搜尋引擎、社群、文件還是部落格,都是我們解決問題的利器。 但是難題往往不在意識,而在於資源:我知道我解決不了,我也知道該求助,可是除了百度,我該向誰求助呢? 因此,本文整理了筆者在學習P

git常用的命令知道有哪些?

sta 破壞 remote 歷史版本 最新 服務器 -- 常用 gin 1.git與svn的區別 1,git是目前世界上最先進的分布式版本控制系統,他沒有中央服務器,每個人的電腦就是一個完整的版本庫,這樣,工作的時候不需要聯網 2,svn是集中式版本控制系統,版本庫是集中

雲計算現在這幾方面發展受關註知道

發揮 軟件工程 條件 情況 開發 基礎 工程 平板 增加 21世紀10年代雲計算作為一個新的技能趨勢現已得到了快速的開展。雲計算現已徹底改動了一個史無前例的工作方式,也改動了傳統軟件工程企業。以下幾個方面可以說是雲計算現階段開展最受重視的幾大方面:   1、雲計算擴展出資價

知道 Java 類是如何被載入的嗎?

一:前言 最近給一個非Java方向的朋友講了下雙親委派模型,朋友讓我寫篇文章深度研究下JVM的ClassLoader,我確實也

知道Java中的CopyOnWriteArrayList嗎?

CopyOnWrite CopyOnWrite是什麼? CopyOnWriteArrayList原始碼分享? CopyOnWriteArrayList使用場景? CopyOnWriteArrayList有什麼優缺點? 如果你是求職者,你想想看怎麼回答上面的問題? 緣由 前段時間面試好多個人,問是否用過Co

AQS原始碼深入分析之條件佇列-知道Java中的阻塞佇列是如何實現的嗎?

本文基於JDK-8u261原始碼分析 ------ # 1 簡介 ![img](https://img2020.cnblogs.com/other/1187061/202011/1187061-20201109170509374-960514910.png) 因為CLH佇列中的執行緒,什麼執行緒獲取

2021-2-19:請問知道 Java 如何高效能操作檔案麼?

一般高效能的涉及到儲存框架,例如 RocketMQ,Kafka 這種訊息佇列,儲存日誌的時候,都是通過 Java File MMAP 實現的,那麼什麼是 Java File MMAP 呢? # 什麼是 Java File MMAP 儘管從**JDK 1.4**版本開始,Java 記憶體對映檔案(Memor

IntelliJ IDEA 常用配置詳細圖解,新手入門

剛剛使用IntelliJ IDEA 編輯器的時候,會有很多設定,會方便以後的開發,磨刀不誤砍柴工。 比如:設定檔案字型大小,程式碼自動完成提示,版本管理,原生代碼歷史,自動匯入包,修改註釋,修改tab的顯示的數量和行數,開啟專案方式,等等一大堆東西。 總結一下,免得下

java工程師必須的linux命令

1.查詢檔案 find / -name filename.txt 根據名稱查詢/目錄下的filename.txt檔案。 find . -name “*.xml” 遞迴查詢所有的xml檔案 2.檢視一個程式是否執行 ps –ef|grep tomcat 檢

JAVA工程師學技能,進階&漲薪的推進器!這份實戰教程請收下

direct 簡單 auto image wechat 處理 並發 它的 一段時間 Netty 作為互聯網中間件的基石,是 JAVA 工程師進階為高級程序員必備的能力之一。也是目前是互聯網中間件領域使用最廣泛最核心的網絡通信框架。 Netty是一個高性能、異步事件驅動的N

知道CPU結構也影響Redis效能嗎?

啦啦啦,我是賣身不賣藝的二哈,ε=(´ο`*)))唉錯啦(我是開車的二哈),我又來了,鐵子們一起開車呀! 今天來分析下CPU結構對Redis效能會有影響嗎? 在進行Redis效能分析的時候,通常我們會考慮下面這些方面,如:   1. 縮短 key 的長度   2.

硬件工程師電路模塊之MOS管應用(轉)

增強 aliyun vgs conn www oot 信號 .com desc **本文你可以獲得什麽? 實際工程應用中常用的MOS管電路(以筆記本主板經典電路為例); 學到實際系統中用到的開關電路模塊以及MOS管非常重要的隔離電路(結合IIC的數據手冊和筆記本主板應

硬件工程師電路之二極管應用上(轉)

dad sset 工作 cdd 速度 快的 產生 pos -c 二極管是最基本的電路器件,硬件工程師經常使用,但你未必能用對,未必能用好。 比如說大家都知道接口部分一般都需要ESD保護,其實TVS瞬變電壓抑制二級管用作ESD保護就極為講究,對於USB3.0, HDMI接

運維工程師的109個Linux命令(4)

linux 小強測試品牌 測試幫日記 點擊鏈接加入QQ群 522720170(免費公開課、視頻應有盡有):https://jq.qq.com/?_wv=1027&k=5C08ATe1 進程管理1.1 crontab1.1.1 功能說明設置計時器。1.1.2 語法crontab [-u &l

開源項目幾點心得,Java架構幾大技術點

動態 xtra 安全 ext hibernate struts 自己 ati 16px 關於學習架構,必須會的幾點技術 1. java反射技術 2. xml文件處理 3. properties屬性文件處理 4. 線程安全機制 5.