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. HBase基础知识摘要

    HBASE 列式存储,设计思想参考BigTable 文档:http://hbase.apache.org/book.html hive适合数据分析,离线任务 hbase大数据实时查询 避免显式锁,提供 ...

  2. JS删除微博

    昨天晚上找回了10年注册的微博,现在瞅瞅,转发过很多傻吊的微博,关注了一堆营销号,不忍直视,动手删吧!开玩笑的,怎么可能手动! 查看自己的所有微博,F12----->console,负责下面代码 ...

  3. 实现多Realm时,可能会出现的问题

    问题背景 在实现多Realm时,扩展了ModularRealmAuthenticator 和 UsernamePasswordToken,于是在MyAuthenticationToken token ...

  4. VMware Workstation Pro 虚拟机安装CentOS-7

    一.下载CentOS-7镜像 我是通过阿里开源镜像站下载的, 下载url:https://mirrors.aliyun.com/centos/7/isos/x86_64/ 下载CentOS-7-x86 ...

  5. Ubuntu下创建apt源

    1. 下载所需安装文件 sudo apt-get install soft name  安装并保存安装文件 或者 sudo apt-get source soft name      只下载安装文件 ...

  6. 【转】Liunx常用命令详解

    Liuux命令查询入口 Linux命令 - 系统信息 命令代码 注释说明 arch 显示机器的处理器架构(1) uname -m 显示机器的处理器架构(2) uname -r 显示正在使用的内核版本 ...

  7. Linux下如何使用X86 CPU的GPIO

    目录 1.前言 2.linux pinctrl子系统 3. pin controller driver 4.手动构造device 1.前言 在arm嵌入式开发中,各个外设具有固定的物理地址,我们可以直 ...

  8. 云计算之路-出海记:蹭一张 aws 船票

    出海记开篇之后,在 aws 上搭建博客园海外站的出海计划今天开始迈出第一步 -- 注册一个 aws 海外区域账号. aws 现在针对新注册用户提供12个月免费套餐(正在园子里推广并提供了专属注册通道) ...

  9. 我用 Python 找出了删除我微信的所有人并将他们自动化删除了

    1. 概述 不知你是否遇到过在微信上给通讯录中的某个人发消息,结果出现了这一幕: 平时一直认为自己的心里素质过硬,不过遇到这种情况 ... 在我缓了半个钟头(半分钟)之后,缓缓拿出了手机,打开微信,找 ...

  10. CF1295E Permutation Separation

    线段树 难得把E想出来,写出来,但却没有调出来(再给我5分钟),我的紫名啊,我一场上紫的大好机会啊 首先考虑是否能将$k$在$1$--$n-1$的每一个的最小代价都求出来 因为$k$从$i$到$i-1 ...