JAVA併發編程:線程併發工具類CountDownLatch與CyclicBarrier的作用、應用場景和實戰

一、CountDownLatch

1、基本概念及作用

JDK解釋:A synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes.

  閉鎖,CountDownLatch 這個類能夠使一個線程等待一個或多個線程完成各自的工作後再執行。
  CountDownLatch 是通過一個計數器來實現的,計數器的初始值爲初始任務的數量。每當完成了一個任務後,計數器的值就會減1,調用CountDownLatch類的countDown()方法。調用CountDownLatch類的await()方法,線程在閉鎖上等待。當計數器值到達0 時,表示所有的其他線程已經完成了任務,然後在閉鎖上等待CountDownLatch.await()方法的線程就可以恢復執行任務。

2、應用場景

  • 實現最大的並行性:有時我們想同時啓動多個線程,實現最大程度的並行性。
    例如,我們想測試一個單例類。如果我們創建一個初始計數爲1 的CountDownLatch,並讓所有線程都在這個鎖上等待,那麼我們可以很輕鬆地完成測試。我們只需調用一次countDown()方法就可以讓所有的等待線程同時恢復執行。
  • 開始執行前等待n 個線程完成各自任務:例如應用程序啓動類要確保在處理用戶請求前,所有N 個外部系統已經啓動和運行了,例如處理excel 中多個表單。

  如下圖所示,比如有兩個線程TW1、TW2需要一直wait Ta、Tb、Tc、Td 4個線程執行完相應業務後調用countDown()方法扣減count,當count被扣減爲0時,TW1、TW2線程再執行後面的業務邏輯。需要特別注意的是使用CountDownLatch時調用countDown()方法執行扣減count的線程數和CountDownLatch初始化的count數不需要保持一致,比如在Td同一個線程內可以扣減2次count。
在這裏插入圖片描述

3、代碼示例

類說明:演示CountDownLatch用法,共4個初始化子線程,5個閉鎖釦除點,扣除完畢後,主線程和業務線程才能繼續執行。

package cn.lspj.ch2.tools;

import java.util.concurrent.CountDownLatch;

/**
 *類說明:演示CountDownLatch用法,
 * 共4個初始化子線程,5個閉鎖釦除點,扣除完畢後,主線程和業務線程才能繼續執行
 */
public class CountDownLatchToExample {

    private static CountDownLatch latch = new CountDownLatch(5);

    static class initialTask implements Runnable {
        @Override
        public void run() {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ",initialTask ready initial work...");
            latch.countDown();
            System.out.println(Thread.currentThread().getName() + ",initialTask contine do other work");
        }
    }

    /**
     * 業務線程等待latch的計數器爲0時執行
     */
    static class BusiTask implements Runnable {
        @Override
        public void run() {
            try {
                latch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ",BusiTask begin do work....");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(100);
                    System.out.println(Thread.currentThread().getName() + ",begin do step1 work...");
                    latch.countDown();
                    System.out.println(Thread.currentThread().getName() + ",step1 work is end ...");

                    Thread.sleep(100);
                    System.out.println(Thread.currentThread().getName() + ",begin do step2 work...");
                    latch.countDown();
                    System.out.println(Thread.currentThread().getName() + ",step2 work is end ...");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        for(int i = 0;i<3;i++){
            new Thread(new initialTask()).start();
        }
        new Thread(new BusiTask()).start();

        latch.await();
        System.out.println(Thread.currentThread().getName() + ",Main is end");
    }
}

執行結果如下:
在這裏插入圖片描述

二、CyclicBarrier

1、基本概念及作用

JDK解釋:A synchronization aid that allows a set of threads to all wait for each other to reach a common barrier point. CyclicBarriers are useful in programs involving a fixed sized party of threads that must occasionally wait for each other. The barrier is called cyclic because it can be re-used after the waiting threads are released.

  CyclicBarrier 的字面意思是可循環使用(Cyclic)的屏障(Barrier)。它要做的事情是讓一組線程到達一個屏障(也可以叫同步點)時被阻塞,直到最後一 個線程到達屏障時,屏障纔會開門,所有被屏障攔截的線程纔會繼續運行。
  CyclicBarrier 默認的構造方法是 CyclicBarrier(int parties),其參數表示屏障攔截的線程數量,每個線程調用 await 方法告訴 CyclicBarrier 我已經到達了屏障,然後當前線程被阻塞。
   CyclicBarrier 還提供一個更高級的構造函數 CyclicBarrie(r int parties,Runnable barrierAction),用於在線程到達屏障時,優先執行 barrierAction,方便處理更復雜的業務場景。

2、應用場景

  • CyclicBarrier 可以用於多線程計算數據,最後合併計算結果的場景。

  如下圖所示,比如有3個線程Ta、Tb、Tc在執行某業務邏輯,只有當3個線程都到達屏障時,纔開始執行後續的邏輯。在這裏插入圖片描述

3、代碼示例

  1. 類說明:演示CyclicBarrier用法,共4個子線程,他們全部完成工作後,交出自己結果,再被統一釋放去做自己的事情。
package cn.lspj.ch2.tools;

import java.util.concurrent.CyclicBarrier;

/**
 *類說明:演示CyclicBarrier用法,共4個子線程,他們全部完成工作後,交出自己結果,再被統一釋放去做自己的事情
 */
public class UseCyclicBarrier {
    private static CyclicBarrier barrier = new CyclicBarrier(4);

    /**
     * 相互等待的子線程
     */
    private static class SubThread implements Runnable{

        @Override
        public void run() {
            try {
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName()+" ....do something ");
                barrier.await(); // 到達屏障,當前線程被阻塞
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName()+" ....do its business ");
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
    }

    public static void main(String[] args) {
        for(int i=0;i<4;i++){
            Thread thread = new Thread(new SubThread());
            thread.start();
        }
    }
}

執行結果如下:
在這裏插入圖片描述

  1. 類說明:演示CyclicBarrier用法,共4個子線程,他們全部完成工作後,交出自己結果, 再被統一釋放去做自己的事情,而交出的結果被另外的線程 barrierAction(CollectThread)拿來拼接字符串。如果多次調用CyclicBarrier的await()方法,將對子線程返回結果進行多次彙總。
package cn.lspj.ch2.tools;

import java.util.Map;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CyclicBarrier;

/**
 * 類說明:演示CyclicBarrier用法,共4個子線程,他們全部完成工作後,交出自己結果,
 * 再被統一釋放去做自己的事情,而交出的結果被另外的線程 barrierAction(CollectThread)拿來拼接字符串。
 * 如果多次調用CyclicBarrier的await()方法,將對子線程返回結果進行多次彙總
 */
public class CyclicBarrierToBarrierAction {

    private static CyclicBarrier barrier = new CyclicBarrier(4,new CollectThread());

    // 存放子線程工作結果的容器
    private static Map<String,String> result = new ConcurrentHashMap<>();

    /**
     * 對子線程結果進行彙總的線程
     */
    private static class CollectThread implements Runnable {

        @Override
        public void run() {
            StringBuilder sbResult = new StringBuilder();
            for(Map.Entry entry : result.entrySet()){
                sbResult.append("[" + entry.getValue()+ "] ");
            }
            System.out.println("result=" + sbResult.toString());
            System.out.println("do other something......");
        }
    }

    /**
     * 相互等待的子線程
     */
    private static class SubThred implements Runnable {

        @Override
        public void run() {

            result.put(Thread.currentThread().getName(),"name=" + Thread.currentThread().getName());
            try {
                Thread.sleep(100);
                System.out.println(Thread.currentThread().getName()+",... do something");
                barrier.await();
                Thread.sleep(100);
                System.out.println(Thread.currentThread().getName()+",... do its business");
                barrier.await(); // 如果多次調用CyclicBarrier的await()方法,將對子線程返回結果進行多次彙總
            } catch (InterruptedException e) {
                e.printStackTrace();
            }catch (BrokenBarrierException e) {
                e.printStackTrace();
            }

        }
    }


    public static void main(String[] args) {
        for(int i=0;i<4;i++){
            new Thread(new SubThred()).start();
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

執行結果如下:

在這裏插入圖片描述


備註:博主微信公衆號,不定期更新文章,歡迎掃碼關注。
在這裏插入圖片描述

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