Java并发工具CountDownLatch的使用和原理

打印 上一主题 下一主题

主题 859|帖子 859|积分 2579

1.等待多线程完成的 CountDownLatch

  CountDownLatch 允许一个或多个线程等待其他线程完成操作。
  假如有这样一个需求:我们需要解析一个 Excel 里多个 sheet 的数据,此时可以考虑使用多线程,每个线程解析一个 sheet 里的数据,等到所有的 sheet 都解析完之后,程序需要提示解析完成。在这个需求中,要实现主线程等待所有线程完成 sheet 的解析操作,最简单的做法是使用 join()方法,如代码清单 1-1 所示。
代码清单1-1 CountDownLatchUseCase.java
  1. public class CountDownLatchUseCase {
  2.     public static void main(String[] args) throws InterruptedException {
  3.         Thread threadA = new Thread(()->{
  4.             System.out.println("parser1 finish");
  5.         },"threadA");
  6.         Thread threadB = new Thread(()->{
  7.             System.out.println("parser2 finish");
  8.         },"threadB");
  9.         // 任务A B 开始执行
  10.         threadA.start();
  11.         threadB.start();
  12.         // 等待AB 完成任务
  13.         threadA.join();
  14.         threadB.join();
  15.     }
  16. }
复制代码
  join 用于让当前执行线程等待 join 线程执行结束。其实现原理是不停检查 join 线程是否存活,如果 join 线程存活则让当前线程永远等待。其中,wait(0)表示永远等待下去,代码片段如下。
    java.lang.Thread#join(long)
  1. // 同步方法
  2. public final synchronized void join(long millis)
  3. throws InterruptedException {
  4.         long base = System.currentTimeMillis();
  5.         long now = 0;
  6.         if (millis < 0) {
  7.                 throw new IllegalArgumentException("timeout value is negative");
  8.         }
  9.         if (millis == 0) {
  10.         // isAlive()方法返回一个boolean值,如果线程已经启动且尚未终止,则返回true;否则,返回false。此次的while循环为了防止被阻塞的线程被意外唤醒,走出这段循环代表线程任务已经执行完毕,线程已经终止。
  11.                 while (isAlive()) {
  12.             // 线程进入等待状态
  13.                         wait(0);
  14.                 }
  15.         } else {
  16.         // 使用join(long millis)参数不为0走这部分逻辑,如果线程没有即时被唤醒,超时时间过后自己进入RUNNABLE状态。
  17.                 while (isAlive()) {
  18.                         long delay = millis - now;
  19.                         if (delay <= 0) {
  20.                                 break;
  21.                         }
  22.                         wait(delay);
  23.                         now = System.currentTimeMillis() - base;
  24.                 }
  25.         }
  26. }
复制代码
  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
  1. public class CountDownLatchUseCase {
  2.     private static CountDownLatch countDownLatch = new CountDownLatch(2);
  3.     public static void main(String[] args) throws InterruptedException {
  4.         Thread threadA = new Thread(()->{
  5.             System.out.println("parser1 finish");
  6.             countDownLatch.countDown();
  7.         },"threadA");
  8.         Thread threadB = new Thread(()->{
  9.             System.out.println("parser2 finish");
  10.             countDownLatch.countDown();
  11.         },"threadB");
  12.         threadA.start();
  13.         threadB.start();
  14.         // 等待任务完成
  15.         countDownLatch.await();
  16.     }
  17. }
复制代码
2.2 方法public void countDown()
  1. // 1.方法 CountDownLatch countDownLatch = new CountDownLatch(2);
  2. # java.util.concurrent.CountDownLatch#CountDownLatch
  3. // 使用构造方法将AQS的同步状态位初始为count
  4. public CountDownLatch(int count) {
  5.         if (count < 0) throw new IllegalArgumentException("count < 0");
  6.         this.sync = new Sync(count);
  7. }
  8. Sync(int count) {
  9.         setState(count);
  10. }
复制代码
3.总结

  CountDownLatch使用主要围绕CountDownLatch.countDown()方法和CountDownLatch.await()方法,使用时先通过CountDownLatch的构造方法设置共享同步变量state的大小,然后让其他“工作”线程完成工作任务时调用countDown方法将state-1,让“等待”线程调用await()方法等待“工作”线程完成任务,当某个“工作”线程完成最后一个任务,并且将state更新为0时,这个“工作”线程会去唤醒同步队列中的第一个“等待”线程,首个“等待”线程被唤醒会“通知”其他“等待”线程,然后他们从await()方法方法,继续执行。
参考 《Java并发编程的艺术》

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

用多少眼泪才能让你相信

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表