[JAVA] Java之CountDownLatch原理全面解析

1785 0
黑夜隐士 2022-11-8 17:44:23 | 显示全部楼层 |阅读模式
目录

    CountDownLatch原理解析
      1. demo展示2. 原理解析
    Java CountDownLatch学习总结
      来源包业务书写示例一般代码示例



CountDownLatch原理解析


1. demo展示

代码逻辑展示了主线程中创建2个子线程分别去执行任务,主线程等2个子线程执行完毕后,再接着执行下面的代码;
常用场景:
分别计算,汇总结果。如,多个线程分别解析excel中的sheet,等待全部解析完毕后汇总结果;
  1. import java.util.concurrent.CountDownLatch;
  2. import java.util.concurrent.TimeUnit;
  3. public class CountDownLatchDemo {
  4.     //定义一个倒计时闩锁
  5.     static CountDownLatch c = new CountDownLatch(2);
  6.     public static void main(String[] args) throws InterruptedException {
  7.         new Thread(() -> {
  8.             try {
  9.                 TimeUnit.MICROSECONDS.sleep(200);
  10.             } catch (InterruptedException e) {
  11.                 e.printStackTrace();
  12.             }
  13.             System.out.println("我是线程1");
  14.             //释放一个
  15.             c.countDown();
  16.         }).start();
  17.         new Thread(() -> {
  18.             try {
  19.                 TimeUnit.MICROSECONDS.sleep(200);
  20.             } catch (InterruptedException e) {
  21.                 e.printStackTrace();
  22.             }
  23.             System.out.println("我是线程2");
  24.             //释放一个
  25.             c.countDown();
  26.         }).start();
  27.         System.out.println("我是主线程,我要等那两个线程执行完毕...");
  28.         //等待倒计时为0
  29.         c.await();
  30.         System.out.println("我是主线程,那两个线程都执行完了");
  31.     }
  32. }
复制代码
输出:
我是主线程,我要等那两个线程执行完毕...
我是线程2
我是线程1
我是主线程,那两个线程都执行完了

2. 原理解析

1.先看构造函数new CountDownLatch(2)做了什么?
这是初始化了AQS子类,并将AQS的状态state设置为传入的2;
  1. public CountDownLatch(int count) {
  2.       if (count < 0) throw new IllegalArgumentException("count < 0");
  3.       this.sync = new Sync(count);
  4.   }
复制代码
2.看c.countDown()做了什么?
它释放了一个共享锁状态,也就是state减1;
  1. public void countDown() {
  2.      sync.releaseShared(1);
  3. }
复制代码
3.再看c.await()做了什么?
await方法是CounDownLatch中定义的,它调用了其内部类Sync(也是AQS的子类)的获取共享锁的方法acquireSharedInterruptibly;


acquireSharedInterruptibly方法中调用了CountDownLatch内部类Sync中实现的获取共享锁的方法tryAcquireShared,返回值不小0就算获取到了锁,await方法就能返回了,如果返回值小于0将会进入阻塞等待;


CountDownLatch内部类Sync中tryAcquireShared的实现很简单,只要state=0就返回1,否则返回-1;上面说了返回一个不小于0的数字,c.await()就相当于获取到了锁,就可以返回了,主线程就可以继续执行了。


通过上面分析,每次c.countDown(),就会将state减1,state=0的时候主线程恢复执行;

Java CountDownLatch学习总结


来源包

同为 java.util.concurrent 下的,即也是并发多线程相关下的类,直译 “倒计时锁存器”,一般用于多线程场景,单一的线程也可以,用于等待多个任务完成后再执行其他操作;
提供方法
await()
    导致当前线程等待,直到锁存器倒数到零,除非该线程是{@linkplain Thread35;interrupt interrupted}即被打断状态。如果当前计数为零,则此方法立即返回。如果当前计数大于零,则当前线程将出于线程调度目的被禁用,并处于休眠状态,直到发生以下两种情况之一:由于调用{@link#countDown}方法,计数达到零;或者其他线程{@linkplain thread#中断}当前线程。
如果当前线程:
    在进入此方法时设置了其中断状态;或者在等待时{@linkplain Thread#interrupt interrupted},则抛出{@link InterruptedException},并清除当前线程的中断状态。
简单说就是当使用了这个方法后当前这一个线程将进入等待状态,直到计数器被减到0或者当前线程被中断,计数器被减到0后,所有等待的线程将被唤醒继续向下执行
  1. await(long timeout, TimeUnit unit)
复制代码
同上,但是指定了等待的超时时间,即线程除了上方两种被唤醒的情况下,等待到超时时间后也会被唤醒
    countDown():当前计数器减一,如果如果减到 0 则唤醒所有等待在这个 CountDownLatch 上的线程。getCount():获取当前计数的数值

业务书写示例

即将需要一会儿处理的业务 list 设置为计数器的大小,
然后对里面的业务数据执行异步操作,处理业务过程中不论是否有异常都需要对计数器减一,最终使用 await 等待所有任务执行完成,执行完成后,将进入后续处理
  1.       final CountDownLatch latch = new CountDownLatch(lists.size());
  2.       for (List<JSONObject> item: lists) {
  3.         executor.submit(new Runnable() {
  4.           @Override
  5.           public void run() {
  6.             // ....... 业务处理
  7.             } catch (Exception e) {
  8.               // 异常处理
  9.             } finally {
  10.               latch.countDown();
  11.             }
  12.           }
  13.         });
  14.       }
  15.       try {
  16.         latch.await();
  17.       } catch (InterruptedException e) {
  18.         log.error("线程被中断", e);
  19.       }
  20.    // lists 处理完成后的其他业务操作
复制代码
一般代码示例
  1. public static void main(String[] args) throws InterruptedException {
  2.     final CountDownLatch downLatch = new CountDownLatch(3);
  3.     Await wait111 = new Await("wait111", downLatch);
  4.     Await wait222 = new Await("wait222", downLatch);
  5.     CountDownStart countDownStart = new CountDownStart(downLatch);
  6.     wait111.start();
  7.     wait222.start();
  8.     Thread.sleep(1000);
  9.     countDownStart.run();
  10.   }
  11. class Await extends Thread{
  12.   private CountDownLatch countDownLatch;
  13.   private String name;
  14.   public Await(String name, CountDownLatch countDownLatch){
  15.     this.name = name;
  16.     this.countDownLatch = countDownLatch;
  17.   }
  18.   @Override
  19.   public void run() {
  20.     System.out.println(name + " start.....");
  21.     System.out.println(name + " run.....");
  22.     try {
  23.       countDownLatch.await();
  24.     } catch (InterruptedException e) {
  25.       e.printStackTrace();
  26.     }
  27.     System.out.println(name + " continue.....run");
  28.   }
  29. }
  30. class CountDownStart extends Thread{
  31.   private CountDownLatch countDownLatch;
  32.   public CountDownStart(CountDownLatch countDownLatch){
  33.     this.countDownLatch = countDownLatch;
  34.   }
  35.   @Override
  36.   public void run() {
  37.     countDownLatch.countDown();
  38.     countDownLatch.countDown();
  39.     countDownLatch.countDown();
  40.     System.out.println("start countDown");
  41.   }
  42. }
复制代码
运行结果:
wait222 start.....
wait222 run.....
wait111 start.....
wait111 run.....
start countDown
wait111 continue.....run
wait222 continue.....run

但是当我把线程等待去除后:
  1.   public static void main(String[] args) throws InterruptedException {
  2.     final CountDownLatch downLatch = new CountDownLatch(3);
  3.     Await wait111 = new Await("wait111", downLatch);
  4.     Await wait222 = new Await("wait222", downLatch);
  5.     CountDownStart countDownStart = new CountDownStart(downLatch);
  6.     wait111.start();
  7.     wait222.start();
  8. //    Thread.sleep(1000);
  9.     countDownStart.run();
  10.   }
复制代码
结果:
start countDown
wait111 start.....
wait111 run.....
wait111 continue.....run
wait222 start.....
wait222 run.....
wait222 continue.....run
另外两个线程线程并没有开始就执行,可能被抢占了,也可能调度优先度不同,实际使用时还是需要多多实验
以上为个人经验,希望能给大家一个参考,也希望大家多多支持中国红客联盟。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

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

本版积分规则

中国红客联盟公众号

联系站长QQ:5520533

admin@chnhonker.com
Copyright © 2001-2025 Discuz Team. Powered by Discuz! X3.5 ( 粤ICP备13060014号 )|天天打卡 本站已运行