1、介绍

本文将介绍CountDownLatch并给出实践中的几个例子,通过使用CountDownLatch我们可以让一个线程阻塞直到其他一个或多个线程执行完成。

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

2、并发编程中的用法

简单的说,CountDownLatch有一个counter属性,它可以按照我们需求递减,我们可以使用它去阻塞一个正被调用的线程直到counter递减为0。

如果我们在做一些并行的处理,我们可以初始化CountDownLatch的counter值为我们即将要运行的线程数,我们可以在每个线程运行结束的时候调用它的countdown方法,这时当前主线程调用await方法会阻塞直到其他worker线程都完成。

3、等待多个线程处理完毕

我们现在尝试通过创建一个Worker线程并设置一个CountDownLatch属性,在线程执行完毕的时候可以通知到我们。

public class Worker implements Runnable {
private List<String> outputScraper;
private CountDownLatch countDownLatch; public Worker(List<String> outputScraper, CountDownLatch countDownLatch) {
this.outputScraper = outputScraper;
this.countDownLatch = countDownLatch;
} @Override
public void run() {
doSomeWork();
outputScraper.add("Counted down");
countDownLatch.countDown();
}
}

我们现在创建一个测试用例来验证主线程会阻塞到工作线程完成。

@Test
public void whenParallelProcessing_thenMainThreadWillBlockUntilCompletion()
throws InterruptedException { List<String> outputScraper = Collections.synchronizedList(new ArrayList<>());
CountDownLatch countDownLatch = new CountDownLatch(5);
List<Thread> workers = Stream
.generate(() -> new Thread(new Worker(outputScraper, countDownLatch)))
.limit(5)
.collect(toList()); workers.forEach(Thread::start);
countDownLatch.await();
outputScraper.add("Latch released"); assertThat(outputScraper)
.containsExactly(
"Counted down",
"Counted down",
"Counted down",
"Counted down",
"Counted down",
"Latch released"
);
}

很明显“Latch released”永远是最后被输出的,因为它依赖其他工作线程执行完对CountDownLatch的释放。

注意如果没有调用await方法,我们没办法保证线程执行的顺序,因此最后的测试结果会随机失败

4、多个线程等待开始运行

接着上面的例子,但这次我们运行上千个线程而不只是5个,其中靠前运行的线程很有可能在我们还没运行靠后线程的时候就已经结束了,这就让我们很难去复现一些并发问题,因为我们没有办法让所有的线程并行开始执行。

为了解决这个问题,这次我们使用CountDownLatch的方式和上面几个例子不太一样,相比阻塞主线程等待子线程运行结束,我们可以阻塞所有子线程直到所有子线程都启动完成。

我们修改上例的run方法让它在正式运行前一直阻塞:

public class WaitingWorker implements Runnable {

    private List<String> outputScraper;
private CountDownLatch readyThreadCounter;
private CountDownLatch callingThreadBlocker;
private CountDownLatch completedThreadCounter; public WaitingWorker(
List<String> outputScraper,
CountDownLatch readyThreadCounter,
CountDownLatch callingThreadBlocker,
CountDownLatch completedThreadCounter) { this.outputScraper = outputScraper;
this.readyThreadCounter = readyThreadCounter;
this.callingThreadBlocker = callingThreadBlocker;
this.completedThreadCounter = completedThreadCounter;
} @Override
public void run() {
readyThreadCounter.countDown();
try {
callingThreadBlocker.await();
doSomeWork();
outputScraper.add("Counted down");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
completedThreadCounter.countDown();
}
}
}

现在我们再修改我们的测试用例,让所有子线程正式执行前一直阻塞,然后打开阻塞让所有子线程运行,最后主线程阻塞直至所有子线程执行完毕。

@Test
public void whenDoingLotsOfThreadsInParallel_thenStartThemAtTheSameTime()
throws InterruptedException { List<String> outputScraper = Collections.synchronizedList(new ArrayList<>());
CountDownLatch readyThreadCounter = new CountDownLatch(5);
CountDownLatch callingThreadBlocker = new CountDownLatch(1);
CountDownLatch completedThreadCounter = new CountDownLatch(5);
List<Thread> workers = Stream
.generate(() -> new Thread(new WaitingWorker(
outputScraper, readyThreadCounter, callingThreadBlocker, completedThreadCounter)))
.limit(5)
.collect(toList()); workers.forEach(Thread::start);
readyThreadCounter.await();
outputScraper.add("Workers ready");
callingThreadBlocker.countDown();
completedThreadCounter.await();
outputScraper.add("Workers complete"); assertThat(outputScraper)
.containsExactly(
"Workers ready",
"Counted down",
"Counted down",
"Counted down",
"Counted down",
"Counted down",
"Workers complete"
);
}

这种模式对重现并发问题非常有用,因为它可以让上千个线程并行执行某一段逻辑。

5、提前结束CountDownLatch

有时我们会碰到在countdown()方法执行前线程终止了,这会导致CountDownLatch永远递减不到0最终导致主线程一直阻塞:

@Override
public void run() {
if (true) {
throw new RuntimeException("Oh dear, I'm a BrokenWorker");
}
countDownLatch.countDown();
outputScraper.add("Counted down");
}

我们来修改下之前的测试用例来说明await()方法会一直阻塞:

@Test
public void whenFailingToParallelProcess_thenMainThreadShouldGetNotGetStuck()
throws InterruptedException { List<String> outputScraper = Collections.synchronizedList(new ArrayList<>());
CountDownLatch countDownLatch = new CountDownLatch(5);
List<Thread> workers = Stream
.generate(() -> new Thread(new BrokenWorker(outputScraper, countDownLatch)))
.limit(5)
.collect(toList()); workers.forEach(Thread::start);
countDownLatch.await();
}

很明显,这不是我们想要的结果,程序还是得继续往前执行而不是永无止境的阻塞。

为了解决这个问题,我们在调用await()方法的时候加上一个timeout的参数:

boolean completed = countDownLatch.await(3L, TimeUnit.SECONDS);
assertThat(completed).isFalse();

这样测试用例最终会超时且await()会返回false。

6、总结

  • 我们展示了怎么使用CountDownLatch来阻塞主线程直至其他线程执行完毕。
  • 我们还展示了它可以帮我们使多个线程并行运行来调试并发问题。

最后这些例子的实现都可以在Github上找到

原文地址:https://www.baeldung.com/java-countdown-latch

JUC并发工具包之CountDownLatch的更多相关文章

  1. 多线程进阶——JUC并发编程之CountDownLatch源码一探究竟

    1.学习切入点 JDK的并发包中提供了几个非常有用的并发工具类. CountDownLatch. CyclicBarrier和 Semaphore工具类提供了一种并发流程控制的手段.本文将介绍Coun ...

  2. JUC并发工具包之CyclicBarrier & CountDownLatch的异同

    1.介绍 本文我们将比较一下CyclicBarrier和CountDownLatch并了解两者的相似与不同. 2.两者是什么 当谈到并发,将这两者概念化的去解释两者是做什么的,这其实是一件很有挑战的事 ...

  3. JUC并发工具包之Semaphore

    目录 Semaphore (JDK) Timed Semaphore (Apache Commons) Semaphore vs. Mutex CodeRepo Semaphore (JDK) 我们使 ...

  4. JUC并发工具包之CyclicBarrier

    1.简介 CyclicBarrier是一个同步器,允许多个线程等待彼此直到达一个执行点(barrier). CyclicBarrier都是在多个线程必须等到彼此都到达同一个执行点后才执行一段逻辑时才被 ...

  5. Java 并发工具包 java.util.concurrent 用户指南

    1. java.util.concurrent - Java 并发工具包 Java 5 添加了一个新的包到 Java 平台,java.util.concurrent 包.这个包包含有一系列能够让 Ja ...

  6. Java并发编程-并发工具包(java.util.concurrent)使用指南(全)

    1. java.util.concurrent - Java 并发工具包 Java 5 添加了一个新的包到 Java 平台,java.util.concurrent 包.这个包包含有一系列能够让 Ja ...

  7. Java 8并发工具包漫游指南

    Java 8并发工具包简介 Java 8并发工具包由3个包组成,分别是java.util.concurrent.java.util.concurrent.atomic和java.util.concur ...

  8. Java_并发工具包 java.util.concurrent 用户指南(转)

    译序 本指南根据 Jakob Jenkov 最新博客翻译,请随时关注博客更新:http://tutorials.jenkov.com/java-util-concurrent/index.html.本 ...

  9. Java 并发工具包 java.util.concurrent 大全

    1. java.util.concurrent - Java 并发工具包 Java 5 添加了一个新的包到 Java 平台,java.util.concurrent 包.这个包包含有一系列能够让 Ja ...

随机推荐

  1. origin添加两个Y轴

    1. 选中X和两个Y 2. 点击Double Y 3. 关掉gap to Symbol,否则Line+Symbol这种显示方式可能显示不出线条 ​

  2. CentOS7 安装telnet-0.17-64.el7.x86_64

    1.安装客服端,服务端,xinetd yum -y install telnet telnet-server xinetd 以上要想完成telnet安装,telnet服务端和xinetd必须安装,至于 ...

  3. Spring入门-----------------属性注入和对象注入

    属性注入即通过setter方法注入bean的属性或依赖对象. 属性注入使用<property>元素,使用name属性指定bean的属性的名称,value属性或<value>子节 ...

  4. Reading Face, Read Health论文阅读笔记

    摘要: 随着计算技术觉.人工智能和移动技术的发展,利用计算机读脸技术去识别每个人每天的健康是可行的.怎么去设计一个基于FRT(face reading technologies)的用于得到每天的保健实 ...

  5. 学习写简单Spring源码demo

    最近在研究怎么实现简单的Spring的源码,通过注解的方式来实现对bean的加载管理. 首先先来看下我的工程结构: (1)spring-common:定义了常用的枚举常量,工具类(如FileUtils ...

  6. GPRS DTU设备常见的问题分析

    在GPRS DTU设备使用的过程中,经常会遇到各种各样的问题,今天就为大家来分析一下在GPRS DTU设备使用时会遇到的一些问题. 1.GPRS模块设置 a.检查串口参数是否与GPRS模块的工作参数一 ...

  7. Android Studio的第一次经历

    第一个简单APP的制作是从xml开始的,通过在java新建一个empty  activity,并在layout里找到对应的xml文件进行编写.每编写一个xml就要事先新建 一个对应的empty  ac ...

  8. .net 实现 一二级分类

    public List<Model.Category> CategoryPid(int id = 0) { string sql = "select * from categor ...

  9. 双重河内塔I

    双重河内塔问题 又称:双重汉诺塔问题 这是<具体数学:计算机科学基础(第2版)>中的一道课后习题 这道题也是挺有意义的,我打算写三篇随笔来讲这个问题 双重河内塔包含 2n 个圆盘,它们有 ...

  10. 基于gin的golang web开发:mysql增删改查

    Go语言访问mysql数据库需要用到标准库database/sql和mysql的驱动.标准库的Api使用比较繁琐这里再引入另一个库github.com/jmoiron/sqlx. go get git ...