CountDownLatch与CyclicBarrier对比理解

  • 第一个场景:

两个线程A、B;假设A线程要等待B线程执行完了之后才执行。在Thread类中提供了一个 join()方法很好能解决这个问题。

Thread.join()方法底层是等待 线程B dead过后才继续执行线程A

如果有这么一个情况,有1个主线程,5个主线程,需要5个子线程都完成里面特定的一个主逻辑之后就返回,告诉主线程自己完成了 , 并且这个时候子服务仍然可以继续执行

那么就引出了今天的主角1:

CountDownLatch

CountDownLatch是Java 1.5版本提供java.util.concurrent的一个类,解决多个线程之间的交互、通知等待这样问题的一个类
代码示例:

public class MyTest1 {
    
    
    public static void main(String[] args) {
    
    
        CountDownLatch countDownLatch=new CountDownLatch(3);// 参数是指实际执行子任务的数量

        IntStream.range(0,3).forEach(i->new Thread(()->{
    
    
            try {
    
    
                Thread.sleep(2000);
                System.out.println("Hello");
            }catch (InterruptedException e){
    
    
                e.printStackTrace();
            }finally {
    
    
                // countDown() 方法就会让 3 这个参数,原子性地减1
                countDownLatch.countDown();
            }
        }).start());

        System.out.println("========启动子线程完毕");

        try {
    
    
            //检测 CountDownLatch 传入的参数 3 是否大于0,大于0就会阻塞
           // countDownLatch.await();
            //重载方法可以很好的规避掉主线程 由于countDown()方法的一个调用不当,或者是失误,而导致无法退出,影响主线程的继续执行
            countDownLatch.await(200, TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }

        System.out.println("-====主线程执行完毕");
    }
}

值得注意的地方

1.首先 new CountDownLatch(3) 传入了一个参数,这个参数是指实际执行子任务的数量

2.countDown() 方法的调用放在了 finnally里面,保证了这个方法一定会被调用。每调用一次这个方法,方法就会让 3 这个参数,原子性地减1
3.await()方法会检测 CountDownLatch 传入的参数 3 是否大于0,大于0就会阻塞,(不可能会小于0),等于0的时候就会释放

4…await()方法有一个重载的方法 await(long timeout, TimeUnit unit) ;重载方法可以很好的规避掉主线程 由于countDown()方法的一个调用不当,或者是失误,而导致无法退出,影响主线程的继续执行

看看CountDownLatch 底层实现原理

在这里插入图片描述
内部内 Sync 继承了 AQS,使用了AQS 里面的tryAcquireSharedNanos 和releaseShared 方法。
哈哈,我大概就能理解到这儿了…

------------------------------------------------我是分隔线-------------------------------------------------------
------------------------------------------------我是分隔线-------------------------------------------------------

  • 第二个场景:

假设有5个线程,(5个线程是同等级的),前面4个线程都到达了一个点,最后一个线程没有到达,那么前面4个线程就会陷入等待状态。直到最后一个线程都到达了这个点之后,这5个线程会同时开始去执行

那么就引出了今天的主角2:

CyclicBarrier (环形的屏障)

CyclicBarrier 是Java 1.5版本提供java.util.concurrent的一个类,解决多个线程之间的交互、通知等待这样的一个类,每个线程没有主次之分

public class MyTest2 {
    
    

    public static void main(String[] args) {
    
    
        CyclicBarrier cyclicBarrier = new CyclicBarrier(3, () -> {
    
    
            System.out.println("Hello World");
        });   //参数是3 是指参与方,意思就是参与到等待的线程的数量,当这三个线程都到达了屏障之前,屏障才会取消掉

        for (int j = 0; j < 2; j++) {
    
    
            for (int i = 0; i < 3; i++) {
    
    
                new Thread(() -> {
    
    
                    try {
    
    
                        Thread.sleep((long) (Math.random() * 2000));

                        int randomInt = new Random().nextInt(500);

                        System.out.println("hello " + randomInt);

                        /**
                         * 1.检查当前屏障的前面是否有三个线程, 如果没有当前线程就会等待;
                         * 2.如果当最后一个线程在屏障前面调用 await方法的那一瞬间,算上自己已经有三个线程了,那么它会通知其余所有等待的线程
                         *  一起冲破屏障,然后一起执行
                         */
                        cyclicBarrier.await();

                        System.out.println("world " + randomInt);

                    } catch (Exception e) {
    
    
                        e.printStackTrace();
                    }

                }).start();
            }
        }
    }
}

值得注意的地方

1.第一个构造方法 new CyclicBarrier(int parties) 有一个参数,参数是3 是指参与方,意思就是参与到等待的线程的数量,当这3个线程都到达了屏障之前,屏障才会取消掉

2.还有一个构造方法 new CyclicBarrier(int parties, Runnable barrierAction) barrierAction叫屏障动作这个参数。当最后一个线程到达屏障之前,就会触发这个参数的执行。在每个线程在执行自己的逻辑代码之前注意是之前,就会执行barrierAction里面的代码
在这里插入图片描述

3.**cyclicBarrier.await()**方法的调用有两层意思

  • 检查当前屏障的前面是否有三个线程, 如果没有当前线程就会等待;
  • 如果当最后一个线程在屏障前面调用 await方法的那一瞬间,算上自己已经有三个线程了,那么它会通知其余所有等待的线程 一起冲破屏障,然后一起执行

4.有一个重载的方法 await(long timeout, TimeUnit unit),如果这个时间设置很短,不会像 CountDownLatch里面的 await带时间的重载方法。反而抛出异常
在这里插入图片描述
第一个异常是:TimeoutException,是等待超时异常,第二个异常:BrokenBarrierException,意思是屏障会被破坏

关于 CyclicBarrier 底层实现逻辑:

1.初始化 CyclicBarrier 中的各种成员变量,包括 parties、count以及 Runnable (可选)

2.当调用 await方法时,底层会先检查计数器是否已经归 0,如果是的话,那么就首先执行可选的 Runnable 接下来开始下一个 generation

3.在下一个分代中,将会重置 count值为 parties ,并且创建新的 Generation

4.同时会调用 Condition 的 signalAll 方法,唤醒所有在屏障前面的等待的线程,在屏障前进行等待

5.如果计数器没有归零,那么当前的调用线程将会通过 Condition 的 await方法,在屏障前进行等待

底层代码:(这里的 trip 是一个 Condition 的对象)
在这里插入图片描述
6.以上所有执行流程均在 lock 锁的控制范围内,不会出现并发安全的情况

底层代码:
在这里插入图片描述

CountDownLatch与CyclicBarrier对比理解!!

CyclicBarrier 构建方法里面的入参和 CountDownLatch 相比,CyclicBarrier的计数器是循环使用的,而CountDownLatch传参的计数器是一次性的

上诉 MyTest2代码用了一个双从 for循环就看到验证了这一点

猜你喜欢

转载自blog.csdn.net/weixin_43582499/article/details/112781427