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. 高精度算法求n阶阶乘

    1 #include "stdio.h" 2 #include "String.h" 3 #define MAX 10000 4 int f[MAX]; 5 v ...

  2. 论文解读《Understanding the Effective Receptive Field in Deep Convolutional Neural Networks》

    感知野的概念尤为重要,对于理解和诊断CNN网络是否工作,其中一个神经元的感知野之外的图像并不会对神经元的值产生影响,所以去确保这个神经元覆盖的所有相关的图像区域是十分重要的:需要对输出图像的单个像素进 ...

  3. 【总结】nginx基础

    一.nginx简介 1.什么是nginx? Nginx 是高性能的 HTTP 和反向代理的服务器,处理高并发能力是十分强大的,支持高达 50,000 个并发连接数.功能:反向代理,负载均衡,动静分离 ...

  4. Mybatis入门 Mybatis存在的意义 解决的问题 基本操作

    Mybatis入门 Mybatis的作用 解决的问题 基本操作 为什么要学MyBatis 我们链接操作数据库需要做的步骤 package Test; import java.sql.*; public ...

  5. SQL Server 列存储索引 第四篇:实时运营数据分析

    实时运营数据分析(real-time operational analytics )是指同时在同一张数据表上执行分析处理和业务处理.分析查询主要是对海量数据执行聚合查询,而事务主要是指对数据表进行少量 ...

  6. ipython notesbook 默认路径修改

    Windows下,一个很简单的解决小方法! 改目录 前面很多回答已经说了怎么修改目录: 1.cmd: jupyter notebook --generate-config 2.找到 ~\.jupyte ...

  7. git 出现 error: bad signature fatal: index file corrupt

    一次大改版,提交了很多代码,但再次提交提交不了,也拉不下来仓库的代码 提示error bad signature fatal: index file corrupt 在项目有.git这同级打开Git ...

  8. Docker(9)- docker pull 命令详解

    如果你还想从头学起 Docker,可以看看这个系列的文章哦! https://www.cnblogs.com/poloyy/category/1870863.html 作用 从镜像仓库中拉取或更新镜像 ...

  9. element UI table show-overflow-tooltip属性更改背景色和字体颜色

    .el-tooltip__popper { width: 80%;/*修改宽度*/ background: #000 !important;/*背景色  !important优先级*/ opacity ...

  10. 某C++神作,就100句话而已

    假设p是指针,当delete p;时,后面一定要p=NULL将p指向空 cin cout cerr 都是iostream类型的对象.cout<<"hello world" ...