1.等待多线程完成的 CountDownLatch
CountDownLatch 允许一个或多个线程等待其他线程完成操作。
假如有这样一个需求:我们需要解析一个 Excel 里多个 sheet 的数据,此时可以考虑使用多线程,每个线程解析一个 sheet 里的数据,等到所有的 sheet 都解析完之后,程序需要提示解析完成。在这个需求中,要实现主线程等待所有线程完成 sheet 的解析操作,最简单的做法是使用 join()方法,如代码清单 1-1 所示。
代码清单1-1 CountDownLatchUseCase.java- public class CountDownLatchUseCase {
- public static void main(String[] args) throws InterruptedException {
- Thread threadA = new Thread(()->{
- System.out.println("parser1 finish");
- },"threadA");
- Thread threadB = new Thread(()->{
- System.out.println("parser2 finish");
- },"threadB");
- // 任务A B 开始执行
- threadA.start();
- threadB.start();
- // 等待AB 完成任务
- threadA.join();
- threadB.join();
- }
- }
复制代码 join 用于让当前执行线程等待 join 线程执行结束。其实现原理是不停检查 join 线程是否存活,如果 join 线程存活则让当前线程永远等待。其中,wait(0)表示永远等待下去,代码片段如下。
java.lang.Thread#join(long)- // 同步方法
- public final synchronized void join(long millis)
- throws InterruptedException {
- long base = System.currentTimeMillis();
- long now = 0;
- if (millis < 0) {
- throw new IllegalArgumentException("timeout value is negative");
- }
- if (millis == 0) {
- // isAlive()方法返回一个boolean值,如果线程已经启动且尚未终止,则返回true;否则,返回false。此次的while循环为了防止被阻塞的线程被意外唤醒,走出这段循环代表线程任务已经执行完毕,线程已经终止。
- while (isAlive()) {
- // 线程进入等待状态
- wait(0);
- }
- } else {
- // 使用join(long millis)参数不为0走这部分逻辑,如果线程没有即时被唤醒,超时时间过后自己进入RUNNABLE状态。
- while (isAlive()) {
- long delay = millis - now;
- if (delay <= 0) {
- break;
- }
- wait(delay);
- now = System.currentTimeMillis() - base;
- }
- }
- }
复制代码 CountDownLatch 的构造函数接收一个 int 类型的参数作为计数器,如果你想等待 N个点完成,这里就传入 N。当我们调用 CountDownLatch 的 countDown 方法时,N 就会减 1,CountDownLatch 的 await 方法会阻塞当前线程,直到 N 变成零。由于 countDown方法可以用在任何地方,所以这里说的 N 个点,可以是 N 个线程,也可以是 1 个线程里的 N 个执行步骤。用在多个线程时,只需要把这个 CountDownLatch 的引用传递到线程里即可。
如果有某个解析 sheet 的线程处理得比较慢,我们不可能让主线程一直等待,所以可以使用另外一个带指定时间的 await 方法——await(long time,TimeUnit unit),这个方法等待特定时间后,就会不再阻塞当前线程。join 也有类似的方法。
计数器必须大于等于 0,只是等于 0 时候,计数器就是零,调用 await 方法时不会阻塞当前线程。CountDownLatch 不可能重新初始化或者修改 CountDownLatch对象的内部计数器的值。一个线程调用 countDown 方法 happen-before,另外一个线程调用 await 方法。
2.CountDownLatch原理解析
2.1 构造方法 public CountDownLatch(int count)
初始化好AQS同步状态为state- public class CountDownLatchUseCase {
- private static CountDownLatch countDownLatch = new CountDownLatch(2);
- public static void main(String[] args) throws InterruptedException {
- Thread threadA = new Thread(()->{
- System.out.println("parser1 finish");
- countDownLatch.countDown();
- },"threadA");
- Thread threadB = new Thread(()->{
- System.out.println("parser2 finish");
- countDownLatch.countDown();
- },"threadB");
- threadA.start();
- threadB.start();
- // 等待任务完成
- countDownLatch.await();
- }
- }
复制代码 2.2 方法public void countDown()
- // 1.方法 CountDownLatch countDownLatch = new CountDownLatch(2);
- # java.util.concurrent.CountDownLatch#CountDownLatch
- // 使用构造方法将AQS的同步状态位初始为count
- public CountDownLatch(int count) {
- if (count < 0) throw new IllegalArgumentException("count < 0");
- this.sync = new Sync(count);
- }
- Sync(int count) {
- setState(count);
- }
复制代码 3.总结
CountDownLatch使用主要围绕CountDownLatch.countDown()方法和CountDownLatch.await()方法,使用时先通过CountDownLatch的构造方法设置共享同步变量state的大小,然后让其他“工作”线程完成工作任务时调用countDown方法将state-1,让“等待”线程调用await()方法等待“工作”线程完成任务,当某个“工作”线程完成最后一个任务,并且将state更新为0时,这个“工作”线程会去唤醒同步队列中的第一个“等待”线程,首个“等待”线程被唤醒会“通知”其他“等待”线程,然后他们从await()方法方法,继续执行。
参考 《Java并发编程的艺术》
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |