我數(shù) 10 下大家一起上
在日常編碼中,Java 并發(fā)編程可是少不了,試試下面這些并發(fā)編程工具類:
今天先帶領(lǐng)大家重溫學(xué)習(xí) CountDownLatch 這個(gè)牛叉的工具類,肝起。
認(rèn)識(shí) CountDownLatch
CountDownLatch是一個(gè)同步工具類,用來(lái)協(xié)調(diào)多個(gè)線程之間的同步,或者說(shuō)起到線程之間通信的作用(非互斥)。
CountDownLatch 能夠使一個(gè)線程在等待另外一些線程完成各自工作之后,再繼續(xù)執(zhí)行。使用一個(gè)計(jì)數(shù)器進(jìn)行實(shí)現(xiàn)。計(jì)數(shù)器初始值為線程的數(shù)量。當(dāng)每一個(gè)線程完成自己任務(wù)后,計(jì)數(shù)器的值就會(huì)減一。當(dāng)計(jì)數(shù)器的值為0時(shí),表示所有的線程都已經(jīng)完成一些任務(wù),然后在CountDownLatch上等待的線程就可以恢復(fù)執(zhí)行接下來(lái)的任務(wù)。
CountDownLatch 的使用
CountDownLatch類使用起來(lái)非常簡(jiǎn)單。
Class 位于:java.util.concurrent.CountDownLatch
下面簡(jiǎn)單介紹它的構(gòu)造方法和常用方法。
構(gòu)造方法
CountDownLatch只提供了一個(gè)構(gòu)造方法:
- // count 為初始計(jì)數(shù)值
- public CountDownLatch(int count) {
- // ……
- }
常用方法
//常用方法1:調(diào)用await()方法的線程會(huì)被掛起,它會(huì)等待直到count值為0才繼續(xù)執(zhí)行
- //常用方法1:調(diào)用await()方法的線程會(huì)被掛起,它會(huì)等待直到count值為0才繼續(xù)執(zhí)行
- public void await() throws InterruptedException {
- // ……
- }
- // 常用方法2:和await()類似,只不過(guò)等待超時(shí)后count值還沒(méi)變?yōu)?的話就會(huì)繼續(xù)執(zhí)行
- public boolean await(long timeout, TimeUnit unit) throws InterruptedException {
- // ……
- }
- // 常用方法3:將count值減1
- public void countDown() {
- // ……
- }
CountDownLatch 的應(yīng)用場(chǎng)景
我們考慮一個(gè)場(chǎng)景:用戶購(gòu)買(mǎi)一個(gè)商品下單成功后,我們會(huì)給用戶發(fā)送各種消息提示用戶『購(gòu)買(mǎi)成功』,比如發(fā)送郵件、微信消息、短信等。所有的消息都發(fā)送成功后,我們?cè)诤笈_(tái)記錄一條消息表示成功。
當(dāng)然我們可以使用單線程去完成,逐個(gè)完成每個(gè)操作,如下圖所示:
但是這樣效率就會(huì)非常低。如何解決單線程效率低的問(wèn)題?當(dāng)然是通過(guò)多線程啦。
使用多線程也會(huì)遇到一個(gè)問(wèn)題,子線程消息還沒(méi)發(fā)送完,主線程可能就已經(jīng)打出『所有的消息都已經(jīng)發(fā)送完畢啦』,這在邏輯上肯定是不對(duì)的。我們期望所有子線程發(fā)完消息主線程才會(huì)打印消息,怎么實(shí)現(xiàn)呢?CountDownLatch就可以解決這一類問(wèn)題。
我們使用代碼實(shí)現(xiàn)上面的需求。
- 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));
- // 主線程阻塞,等待所有子線程發(fā)完消息
- countDownLatch.await();
- // 所有子線程已經(jīng)發(fā)完消息,計(jì)數(shù)器為0,主線程恢復(fù)
- 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 {
- // 線程發(fā)送消息
- System.out.println("Send " + messageName);
- try {
- TimeUnit.SECONDS.sleep(1);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- } finally {
- // 發(fā)完消息計(jì)數(shù)器減 1
- countDownLatch.countDown();
- }
- }
- }
- }
程序運(yùn)行結(jié)果:
- main thread: Success to place an order
- Send email
- Send wechat
- Send sms
- main thread: all message has been sent
從運(yùn)行結(jié)果可以看到主線程是在所有的子線程發(fā)送完消息后才打印,這符合我們的預(yù)期。
CountDownLatch 的限制
CountDownLatch是一次性的,計(jì)算器的值只能在構(gòu)造方法中初始化一次,之后沒(méi)有任何機(jī)制再次對(duì)其設(shè)置值,當(dāng)CountDownLatch使用完畢后,它不能再次被使用。