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. elasticsearch启动报错

    1,max file descriptors [4096] for elasticsearch process is too low, increase to at least [65536] 查看 ...

  2. 安装jdk及安装多版本jdk

    目录 由于要使用多个版本jdk,所以看下如何在一台电脑安装多个版本jdk 当然,如果你只需要安装一个jdk,本文也适合你,只需要在JAVA_HOME值填你jdk安装的目录即可 一.首先安装好不同的jd ...

  3. Elasticsearch 注册windows服务后,服务启动失败,意外终止

    直接双击elasticsearch.bat可以成功启动,注册成服务后就启动失败 从网上查找问题,发现是jdk版本的问题,用ES自带的jdk就可以启动成功. 默认ES会先找JAVA_HOME环境变量,如 ...

  4. 转载:Pycharm的常用快捷键

    一直想着找一下pycharm的快捷键,但是每次都忘记找了,这次刚好碰到一个很全的,就直接借用别人的来当作自己的笔记ba 转载来源:https://www.cnblogs.com/liangmingsh ...

  5. Collection迭代器Iterator的使用

    package com.cx.Collecion; import java.util.ArrayList; import java.util.Collection; import java.util. ...

  6. 巧用IDM工具 快捷下载ASTER GDEM v3高程数据

    ASTER GDEM v3是NASA推出的30米高清DEM,覆盖了几乎全部的地球陆地.那么,在NASA官网怎么下载ASTER GDEM v3的地形高程数据呢? 首先,你需要注册一个nasa的账号 注册 ...

  7. 【JVM第一篇--类加载机制】类加载过程

    写在前面的话:本文是在观看尚硅谷JVM教程后,整理的学习笔记.其观看地址如下:尚硅谷2020最新版宋红康JVM教程 一.什么是类加载过程 (1).概述 我们编写的类(.java文件)会被编译器(如ja ...

  8. win7-64位 jdk安装

    1.jdk安装 jdk安装主要是进行jdk以及jre安装,注意jre需要安装到一个空文件夹内即可. 官网地址:http://www.oracle.com/technetwork/java/javase ...

  9. 定制ubuntu的时候修改proseed

    一个参数的修改 d-i clock-setup/utc-auto boolean false (不用utc) d-i clock-setup/ntp boolean false (不时间同步) d-i ...

  10. mysql之数据库备份

    1.可视化工具Navicat for mysql进行操作数据库备份 (1)备份数据库 (2)将备份的数据库进行加载