写点什么

Java 并发基础(三):再谈 CountDownLatch

用户头像
看山
关注
发布于: 2021 年 04 月 05 日
Java 并发基础(三):再谈 CountDownLatch

你好,我是看山。


java.util.concurrent.CountDownLatch 是 JDK 1.5 提供的一个同步辅助类:在一组正在其他线程中的操作执行完成之前,它允许一个或多个线程一直等待。


CountDownLatch 是一个通用同步工具,它有很多用途。用 1 初始化的 CountDownLatch 可以用作一个简单的开/关锁存器或入口:在某一线程调用 countDown 打开入口前,所有调用 await 的线程都一直在入口处等待。用 N(N>=1) 初始化的 CountDownLatch 可以使一个线程在 N 个线程完成某项操作之前一直等待,或者使其在某项操作完成 N 次之前一直等待。


CountDownLatch 的一个有用特性是,在计数达到 0 之前,它不会阻塞调用 countDown 方法的线程继续执行,它只是阻止所有调用 await 的线程继续执行 await 后面的操作。


CountDownLatch 有两种典型用法:


  1. 有两个计数器,一个启动信号,一个完成信号。

  2. 将一个问题分成 N 个部分,需要执行的 N 个子部分定义为 Runnable,然后将所有 Runnable 加入到 Executor 队列中。当所有的 N 个子部分完成后,等待的线程将会通过 await 方法继续执行后续操作。

两个计数器

示例代码:


public class CountDownLatchTest {    public static void main(String[] args) throws InterruptedException {        CountDownLatch startSignal = new CountDownLatch(1);// 先行条件        CountDownLatch doneSignal = new CountDownLatch(N);
for (int i = 0; i < N; ++i) { new Thread(new Worker(startSignal, doneSignal)).start(); }
doSomethingElse(); startSignal.countDown();// 开始 doSomethingElse(); doneSignal.await();// 等待所有操作结束 }}
class Worker implements Runnable { private final CountDownLatch startSignal; private final CountDownLatch doneSignal;
Worker(CountDownLatch startSignal, CountDownLatch doneSignal) { this.startSignal = startSignal; this.doneSignal = doneSignal; }
@Override public void run() { try { startSignal.await();// 等待先行条件结束 doWork(); doneSignal.countDown(); } catch (InterruptedException ex) { } }
void doWork() {...}}
复制代码


在示例代码中,startSignal 为先决条件,此处初始计数为 1,是一个简单的开关。比如田径比赛中的百米跑,发令枪响前,运动员都在等待;发令枪响后,运动员开始比赛,等到最后一名运动员到达终点,比赛结束。


简单的代码实现为:


import java.util.Random;import java.util.concurrent.CountDownLatch;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.TimeUnit;
public class CountDownLatchTest { public static void main(String[] args) throws InterruptedException { final CountDownLatch begin = new CountDownLatch(1); final CountDownLatch end = new CountDownLatch(3); final ExecutorService exec = Executors.newFixedThreadPool(3);
for (int index = 0; index < 3; index++) { exec.submit(new Runner(index + 1, begin, end)); }
System.out.println("各就各位。"); System.out.println("预备。"); System.out.println("嘭。"); begin.countDown(); end.await(); System.out.println("比赛结束,准备颁奖。"); exec.shutdown(); }}
class Runner implements Runnable { private int no; private CountDownLatch begin; private CountDownLatch end;
public Runner(int no, CountDownLatch begin, CountDownLatch end) { this.no = no; this.begin = begin; this.end = end; System.out.println("No." + no + "到达起跑线。"); }
@Override public void run() { try { begin.await();// 等待发令枪响 System.out.println("No." + no + "向前飞奔着。"); TimeUnit.SECONDS.sleep(new Random().nextInt(10));// 奔跑的过程中。 } catch (InterruptedException e) { } System.out.println("No." + no + "到达终点。"); end.countDown(); }}
复制代码


引申开来,开始定义一系列的执行的链条,第一个没有先决条件,直接执行,第二个以第一个为先决条件,以此类推。我是懒人,不做太多赘述,我一个朋友的文章中写的不错:利用 CountDownLatch 同步辅助类进行线程同步

一个计数器

一个计数器的情况自己感觉情况比较单一,就是主线程等待子线程结束,再继续执行。这里的主线程、子线程是相对而言的,可能主线程本身是另一个线程的子线程。


示例代码:


public class CountDownLatchTest6 {    public static void main(String[] args) throws InterruptedException {        CountDownLatch doneSignal = new CountDownLatch(3);        ExecutorService e = Executors.newCachedThreadPool();
for (int i = 0; i < 3; ++i) { e.execute(new Worker(doneSignal, i)); } doSomethingElse(); doneSignal.await();// 等待子线程结束 doSomethingElse(); e.shutdown(); }}
class Worker implements Runnable { private final CountDownLatch doneSignal; private final int id;
Worker(CountDownLatch doneSignal, int id) { this.doneSignal = doneSignal; this.id = id; }
@Override public void run() { doWork(); doneSignal.countDown(); }
void doWork() {...}}
复制代码


示例代码中的 doSomethingElse 方法可以是一些业务逻辑代码,根据具体功能发生变化。


对于这种方式可以参看上一篇中关于老板和工人的例子中的第二种解决方法 主线程等待子线程结束,这里也不做赘述。




你好,我是看山,公众号:看山的小屋,10 年老猿,Apache Storm、WxJava、Cynomys 开源贡献者。游于码界,戏享人生。


个人主页:https://www.howardliu.cn

个人博文:Java 并发基础(三):再谈 CountDownLatch



发布于: 2021 年 04 月 05 日阅读数: 16
用户头像

看山

关注

公众号「看山的小屋」 2017.10.26 加入

游于码界,戏享人生。 未来不迎,当时不杂,既过不恋。

评论

发布
暂无评论
Java 并发基础(三):再谈 CountDownLatch