CountDownlatch允许一个线程或者多个线程等待其他线程完成操作。
假如有这样一个需求:
我们需要解析一个Excel里面的多个sheet的数据,此时,可以考虑使用多线程,每个线程解析一个sheet里的数据,等到所有sheet都解析完了,程序需要提示解析完成。
使用CountDownLatch实现如下:
import java.util.concurrent.CountDownLatch;
/**
* @Description
* @Author DJZ-WWS
* @Date 2019/5/27 14:57
*/
public class TestCountDownLatch {
static CountDownLatch countDownLatch = new CountDownLatch(2);
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第一个sheet解析完毕");
//没调用一次这个方法,构造器里的初始值就会减一
countDownLatch.countDown();
}).start();
new Thread(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第二个sheet解析完毕");
//没调用一次这个方法,构造器里的初始值就会减一
countDownLatch.countDown();
}).start();
System.out.println("主线开始等待");
countDownLatch.await();
System.out.println("所有的sheet解析完毕,继续执行后面的操作");
}
}
执行结果如下:
CountDownLatch的构造器接收一个int型的参数作为计数器,如果你想等待N个点完成,这里就传入N。
当我们调用CountDownLatch的countDown方法时,N就会减一,他的await方法会阻塞当前线程,直到N变成0。
由于countDown方法可以用在任何地方,这里说的N个点,可以是N个线程,也可以是1个线程里面的N个操作。用在多线程时,只需要把这个CountdownLatch的引用传递到线程里即可。
如果解析某个sheet的线程处理的比较慢,我们不可能让主线程一直等待,所有可以使用另外一个带指定时间的await方法-await(long time,TimeUnit unit)
注意计数器必须大于等于0,只是等于0的时候计数器就是0.调用await方法时不会阻塞当前线程。countDownLatch不可能重新初始化或者修改CountDownLatch内部的计数器的值。
下面是另一个类似的例子:
package juc.currnet;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @Description 该程序用来模拟发送命令与执行命令,主线程代表指挥官,新建3个线程代表战士,战士一直等待着指挥官下达命令,
* *若指挥官没有下达命令,则战士们都必须等待。一旦命令下达,战士们都去执行自己的任务,指挥官处于等待状态,战士们任务执行完毕则报告给
* @Author DJZ-WWS
* @Date 2019/4/13 15:25
*/
public class CountDownLatchTest {
public static void main(String[] args) {
ExecutorService service = Executors.newCachedThreadPool();
//模拟命令
CountDownLatch cdOrder = new CountDownLatch(1);
//模拟战士,多个线程
CountDownLatch answer = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
//战士待命状态
System.out.println("线程" + Thread.currentThread().getName() + "正在准备待命");
//个人理解,到目前为止还没有向线程池提交任务,等待的线程为主xianc
cdOrder.await();
System.out.println("线程" + Thread.currentThread().getName() + "已接受命令");
Thread.sleep((long) (Math.random() * 10000));
System.out.println("线程" + Thread.currentThread().getName() + "回应命令处理结果");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
answer.countDown();
}
}
};
service.execute(runnable);//为线程池添加任务
}
try {
Thread.sleep((long) (Math.random() * 10000));
System.out.println("线程" + Thread.currentThread().getName() + "即将发布命令");
cdOrder.countDown(); //发送命令,cdOrder减1,处于等待的战士们停止等待转去执行任务。
System.out.println("线程" + Thread.currentThread().getName() + "已发送命令,正在等待结果");
answer.await(); //命令发送后指挥官处于等待状态,一旦cdAnswer为0时停止等待继续往下执行
System.out.println("线程" + Thread.currentThread().getName() + "已收到所有响应结果");
} catch (Exception e) {
e.printStackTrace();
} finally {
}
service.shutdown(); //任务结束,停止线程池的所有线程
}
}
结果如下:
现在依旧记得刚刚来这家公司的时候我组长面试问的我这个类,我记得我当时举了一个例子:
模拟一家人去吃饭,人去的时候不确定,必须等到人到齐的时候才可以吃饭,这个时候就可以使用这个工具类。当时我是这么答得,也没有再继续往下问了。
参考:《java并发编程的艺术》