天天看點

『死磕Java并發程式設計系列』并發程式設計工具類之CountDownLatch認識 CountDownLatchCountDownLatch 的使用CountDownLatch 的應用場景CountDownLatch 的限制

《死磕 Java 并發程式設計》系列連載中,大家可以關注一波:

👍🏻『死磕Java并發程式設計系列』 01 十張圖告訴你多線程那些破事

『死磕Java并發程式設計系列』 02 面試官:說說什麼是Java記憶體模型?

『死磕Java并發程式設計系列』 03 面試必問的CAS原理你會了嗎?

『死磕Java并發程式設計系列』 04 面試官:說說Atomic原子類的實作原理?

👍🏻『死磕Java并發程式設計系列』 05 圖解Java中那18 把鎖

在日常編碼中,Java 并發程式設計可是少不了,試試下面這些并發程式設計工具類。

今天先帶領大家一起重溫學習 CountDownLatch 這個牛叉的工具類。

認識 CountDownLatch

CountDownLatch

是一個同步工具類,用來協調多個線程之間的同步,或者說起到線程之間通信的作用(非互斥)。

CountDownLatch 能夠使一個線程在等待另外一些線程完成各自工作之後,再繼續執行。使用一個計數器進行實作。計數器初始值為線程的數量。當每一個線程完成自己任務後,計數器的值就會減一。當計數器的值為0時,表示所有的線程都已經完成一些任務,然後在CountDownLatch上等待的線程就可以恢複執行接下來的任務。

『死磕Java并發程式設計系列』并發程式設計工具類之CountDownLatch認識 CountDownLatchCountDownLatch 的使用CountDownLatch 的應用場景CountDownLatch 的限制

CountDownLatch 的使用

CountDownLatch類使用起來非常簡單。

Class 位于:

java.util.concurrent.CountDownLatch

下面簡單介紹它的構造方法和常用方法。

構造方法

CountDownLatch隻提供了一個構造方法:

// count 為初始計數值
public CountDownLatch(int count) {
  // ……
}
           

常用方法

//常用方法1:調用await()方法的線程會被挂起,它會等待直到count值為0才繼續執行
public void await() throws InterruptedException {
  // ……
}   

// 常用方法2:和await()類似,隻不過等待逾時後count值還沒變為0的話就會繼續執行
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { 
  // ……
}

// 常用方法3:将count值減1
public void countDown() {
  // ……
}  
           

CountDownLatch 的應用場景

我們考慮一個場景:使用者購買一個商品下單成功後,我們會給使用者發送各種消息提示使用者『購買成功』,比如發送郵件、微信消息、短信等。所有的消息都發送成功後,我們在背景記錄一條消息表示成功。

當然我們可以使用單線程去完成,逐個完成每個操作,如下圖所示:

『死磕Java并發程式設計系列』并發程式設計工具類之CountDownLatch認識 CountDownLatchCountDownLatch 的使用CountDownLatch 的應用場景CountDownLatch 的限制

但是這樣效率就會非常低。如何解決單線程效率低的問題?當然是通過多線程啦。

使用多線程也會遇到一個問題,子線程消息還沒發送完,主線程可能就已經打出『所有的消息都已經發送完畢啦』,這在邏輯上肯定是不對的。我們期望所有子線程發完消息主線程才會列印消息,怎麼實作呢?CountDownLatch就可以解決這一類問題。

『死磕Java并發程式設計系列』并發程式設計工具類之CountDownLatch認識 CountDownLatchCountDownLatch 的使用CountDownLatch 的應用場景CountDownLatch 的限制

我們使用代碼實作上面的需求。

import java.util.concurrent.*;

public class OrderServiceDemo {

    public static void main(String[] args) throws InterruptedException {
        System.out.println("main thread: Success to place an order");

        int count = 3;
        CountDownLatch countDownLatch = new CountDownLatch(count);

        Executor executor = Executors.newFixedThreadPool(count);
        executor.execute(new MessageTask("email", countDownLatch));
        executor.execute(new MessageTask("wechat", countDownLatch));
        executor.execute(new MessageTask("sms", countDownLatch));

        // 主線程阻塞,等待所有子線程發完消息
        countDownLatch.await();
        // 所有子線程已經發完消息,計數器為0,主線程恢複
        System.out.println("main thread: all message has been sent");
    }

    static class MessageTask implements Runnable {
        private String messageName;
        private CountDownLatch countDownLatch;

        public MessageTask(String messageName, CountDownLatch countDownLatch) {
            this.messageName = messageName;
            this.countDownLatch = countDownLatch;
        }

        @Override
        public void run() {
            try {
                // 線程發送消息
                System.out.println("Send " + messageName);
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } finally {
                // 發完消息計數器減 1
                countDownLatch.countDown();
            }
        }
    }
}
           

程式運作結果:

main thread: Success to place an order
Send email
Send wechat
Send sms
main thread: all message has been sent
           

從運作結果可以看到主線程是在所有的子線程發送完消息後才列印,這符合我們的預期。

CountDownLatch 的限制

CountDownLatch是一次性的,電腦的值隻能在構造方法中初始化一次,之後沒有任何機制再次對其設定值,當CountDownLatch使用完畢後,它不能再次被使用。

大家學會了麼?後面會接着講剩餘的幾種并發工具類,拭目以待吧~

『死磕Java并發程式設計系列』并發程式設計工具類之CountDownLatch認識 CountDownLatchCountDownLatch 的使用CountDownLatch 的應用場景CountDownLatch 的限制

我是雷小帥,愛了~