CountDownLatch 协调线程

JDK8 都快要出来了,在 JDK 5 中仍有许多好宝贝值得去挖掘。提到 JDK5 我们或许只知道它给了我们泛型,其实还有那个并发包 java.util.concurrent 却不那么引人注目,其实就是 NIO。

若是并发包是在某个 JDK 版本中单独奉上,反响就不同了,想想 JDK 6 似乎未带来多少改变--至少对于编程者来说没有明显感受。java.util.concurrent 包中的东西对于我们处理线程带来了很大的便利,例如线程池,线程同步,Future, Callable 等。

这里我记录一下 CountDownLatch 的使用,在此之前在处理

线程 A 等待线程 B,C,D 全部执行完后才继续执行 (比如要每个线程都访问一个 Web 服务,等所有的请求响应成功后进行结果处理)

这样场景的时候,我一般能想到的办法是,初始一个计数器,线程 B,C,D 各自初始化的时候,计数器加一,然后 A 线程等待,每个线程执行完后计数器减一,当计数器为 0 时表明所有任务执行完毕,就通知 A 可以开始运作起来。但这样的方案还是得小心的处理好同步的问题。

这可以用 JDK 5  java.util.concurrent.CountDownLatch,它的实现原理基本与上一致。也是需要任务在执行完毕后执行一下 countDown() 方式使得计数减一,当为 0 时,A 被唤醒。CountDown 就是倒计数,Latch 是门拴的意思,可以理解为某一线程等待所有它关注的线程全部出去(或进来)后把门拴上。

看个示例:
 1package cc.unmi.test.concurrent;
 2
 3import java.util.concurrent.CountDownLatch;
 4import java.util.concurrent.TimeUnit;
 5import java.util.logging.Level;
 6import java.util.logging.Logger;
 7
 8public class TestCountDownLatch {
 9
10    public static void main(String args[]) {
11       final CountDownLatch latch = new CountDownLatch(1);
12       Thread cacheService = new Thread(new Service("CacheService", 1000, latch));
13       Thread alertService = new Thread(new Service("AlertService", 1000, latch));
14
15       cacheService.start(); //separate thread will initialize CacheService
16       alertService.start(); //another thread for AlertService initialization
17
18       try{
19            latch.await(1000, TimeUnit.SECONDS);  //main thread is waiting on CountDownLatch to finish
20            System.out.println("All services are up, Application is starting now");
21       }catch(InterruptedException ie){
22
23       }
24    }
25
26}
27
28class Service implements Runnable{
29    private final String name;
30    private final int timeToStart;
31    private final CountDownLatch latch;
32
33    public Service(String name, int timeToStart, CountDownLatch latch){
34        this.name = name;
35        this.timeToStart = timeToStart;
36        this.latch = latch;
37    }
38
39    @Override
40    public void run() {
41        try {
42            Thread.sleep(timeToStart);
43        } catch (InterruptedException ex) {
44            Logger.getLogger(Service.class.getName()).log(Level.SEVERE, null, ex);
45        }
46        System.out.println( name + " is Up");
47        latch.countDown(); //reduce count of CountDownLatch by 1
48    }
49
50}
输出是:

AlertService is Up
CacheService is Up
All services are up, Application is starting now

PlayFramework 的 controller 中的 await 和这很相拟,它较喜欢用 Promise 这个词。

CountDownLatch 变成 0 后就不能重用了,如果想要能重用的就使用 java.util.concurrent.CyclicBarrier 吧,它用到了 ReentrantLock 的概念。

参考:1. What is CountDownLatch in Java - Concurrency Example Tutorial
            2. What is CyclicBarrier Example in Java 5 – Concurrency Tutorial 永久链接 https://yanbin.blog/countdownlatch-threads/, 来自 隔叶黄莺 Yanbin's Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。