【JUC 并发】CountDownLatch

N 个线程同时完成某项任务时,如何知道他们都已经执行完毕了。

一、CountDownLatch

CountDownLatchJDK 1.5开始concurrent包里提供的,并发编程工具类。
可以把 CountDownLatch 看作一个多线程控制工具类,用于协调多个线程的同步,能让一个线程在等待其他线程执行完任务后,再继续执行。内部是通过一个计数器去完成实现。

1.1 构造函数

1
2
3
4
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}

count代表需要执行的线程数量,也是同步计数器初始化的数量,只有当同步计数值为0,主线程才会向下执行。

1.2 主要方法

  1. void await():调用改方法的线程会被挂起,直到count值为0;
  2. boolean await(long timeout, TimeUnit unit):与上面的类似,只不过等待一定的时间后count值还没变为0的话就会继续执行;
  3. void countDown():在计数值 > 0的情况下,每当一个线程完成任务,计数减去1;
  4. long getCount():获取计数器的值。

当计数器应该为0,所有的线程执行完自己的任务。在CountDownLatch等待的线程,可以继续执行的任务。

二、CountDownLatch的使用

适用于一个任务的执行需要等待其他任务执行完毕,方可执行的场景。

CountDownLatch是一次性的,只能通过构造方法设置初始计数量,计数完了无法进行复位,不能达到复用。

2.1 CountDownLatch应用场景例子

丈夫和妻子一块儿去买菜,假设买蔬菜需要5秒,买肉需要3秒,如果丈夫和妻子分开去买,然后一块儿回家。

  • 妻子去蔬菜
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class WifeTask implements Runnable {
protected Logger logger = LoggerFactory.getLogger(getClass());

private CountDownLatch countDownLatch;

public WifeTask(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}

@Override
public void run() {
try {
// 买蔬菜需要5秒
TimeUnit.SECONDS.sleep(5);
logger.info("妻子蔬菜买好了");
} catch (InterruptedException e) {
logger.info("妻子蔬菜没买到");
} finally {
if (countDownLatch != null) {
countDownLatch.countDown();
}
}
}
}
  • 丈夫去买肉
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class HusbandTask implements Runnable {
protected Logger logger = LoggerFactory.getLogger(getClass());

private CountDownLatch countDownLatch;

public HusbandTask(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}

@Override
public void run() {
try {
// 买肉需要3秒
TimeUnit.SECONDS.sleep(3);
logger.info("丈夫肉买好了");
} catch (InterruptedException e) {
logger.info("丈夫肉没买到");
} finally {
if (countDownLatch != null) {
countDownLatch.countDown();
}
}
}
}
  • 买好菜,在指定地点集合回家(回到主线程),计算耗时
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class CountDownLatchTest {
protected Logger logger = LoggerFactory.getLogger(getClass());

@Test
public void countDownLatchDemo() throws InterruptedException {
Long startTime = System.currentTimeMillis();
CountDownLatch countDownLatch = new CountDownLatch(2);
logger.info("夫妻分头去买菜");
// 夫妻分头去买菜,夫妻买蔬菜(需要5秒),丈夫卖肉(需要3秒)
Executor executor = Executors.newFixedThreadPool(2);
executor.execute(new HusbandTask(countDownLatch));
executor.execute(new WifeTask(countDownLatch));
// 挂起任务,等菜买好了再一起回家
countDownLatch.await();
logger.info("菜买好了,夫妻双双把家还,买菜合计耗时:{}毫秒", (System.currentTimeMillis() - startTime));
}
}
  • 结果打印如下
1
2
3
4
21:36:42.964 ... - 夫妻分头去买菜
21:36:45.973 ... - 丈夫肉买好了
21:36:47.972 ... - 妻子蔬菜买好了
21:36:47.973 ... - 菜买好了,夫妻双双把家还,买菜合计耗时:5011毫秒

通过CountDownLatch初始化计数器值为2,让夫妻两人各自去买菜,完成一项,计数器值减1,都买好后,计数器值为0,回到主线程,一起回家。

2.2 CountDownLatch使用以及注意点

  1. 一旦count达到零,不能再用CountDownLatch(即不可复用)。
  2. 主线程通过调用CountDownLatch.await()方法等待Latch,而其他线程调用CountDownLatch.countDown()以通知它们已完成。
  3. 计数器必须和要执行的线程数匹配,如果计数器大于了要执行的线程数目,那么count最后不会为0,调用的主线程也不会继续执行,主线程将会一直处于等待状态。

【Github 示例源码】

更多 Java 笔记,详见【Java 知识笔记本】,欢迎提供想法建议。

Van wechat
最新文章,欢迎您扫一扫上面的微信公众号!
-------------    本文结束  感谢您的阅读    -------------