深入理解CyclicBarrie

835 阅读4分钟

深入理解CyclicBarrier

其他知识点

Java 多线程基础
深入理解aqs
ReentrantLock用法详解
深入理解信号量Semaphore
深入理解并发三大特性
并发编程之深入理解CAS
深入理解CountDownLatch
Java 线程池

CyclicBarrier 用法详解

CyclicBarrier 用法详解

CyclicBarrier 原理

例子debug一下 走走流程

运动员t1 t2 线程以及裁判main线程一起准备好了 才能比赛。


    public static void main(String[] args) throws BrokenBarrierException, InterruptedException {


       CyclicBarrier cyclicBarrier = new CyclicBarrier(3);


        new Thread(() -> {
            try {
                Thread.sleep(10_000);
                System.out.println("t1 在准备 ");
                cyclicBarrier.await();   // 等另外一个一个线程准备好 然后开始做事情
                System.out.println("t1 准备好了  ");
            } catch (InterruptedException | BrokenBarrierException e) {
                e.printStackTrace();
            }

        },"t1").start();

        new Thread(() -> {
            try {
                Thread.sleep(5_000);
                System.out.println("t2 在准备  ");
                cyclicBarrier.await(); // 等另外一个一个线程准备好 然后开始做事情
                System.out.println("t2 准备好了  ");
            } catch (InterruptedException | BrokenBarrierException e) {
                e.printStackTrace();
            }
        },"t2"  ).start();

        System.out.println(" 裁判 在准备 ");
        cyclicBarrier.await();
        System.out.println(" 裁判 准备好了 ");


        while (true){
            if (cyclicBarrier.getNumberWaiting()==0){
                System.out.println(" 时间到: 开始比赛");
                return;
            }
        }


    }

运行结果

在这里插入图片描述

debug 运行 首先main线程

在这里插入图片描述
不需要等待时间
在这里插入图片描述

具体方法 private int dowait(boolean timed, long nanos)

可以看到这里用了 ReentrantLock 加锁,计数减一

在这里插入图片描述

阻塞main线程

这里 trip 是一个条件队列

private final Condition trip = lock.newCondition();
在这里插入图片描述

可以看到 这里创建了一个条件 node节点

在这里插入图片描述

addConditionWaiter 方法
在这里插入图片描述
然后 LockSupport.park(this); 阻塞当前线程

可以看到main线程已经在wait了 ,现在来走t1线程

在这里插入图片描述

继续走上面的减计数逻辑,此时计数还是不等于 0

在这里插入图片描述

还是会走waite条件队列方法

在这里插入图片描述

新建当前线程node条件节点,以及入队

在这里插入图片描述
然后继续park
在这里插入图片描述

接下来t2线程 await

在这里插入图片描述
会发现此时t2线程计数为 0,执行 nextGeneration();方法在这里插入图片描述

当前t2线程执行 signalAll 方法 通知全部?

count = parties; 重置计数器
在这里插入图片描述

进入方法,获取头结点,

在这里插入图片描述
执行 doSignalAll(first) 方法
获取头结点 将指向下一个指针置为null
在这里插入图片描述
transferForSignal方法

发现条件节点状态先置为 0 然后新建了一个 Node 节点 Node p = enq(node);

在这里插入图片描述
新建node节点

这段代码很熟悉,是aqs里构建同步队列以及入队的方法,可以明白t2线程将条件队列里所有的节点转为同步队列

Node p = enq(node);

t2 最后执行unlock

在这里插入图片描述

释放锁
在这里插入图片描述
这里是也是aqs里的释放锁 逻辑,同时将获取锁的线程置为null

在这里插入图片描述
唤醒其他线程

在这里插入图片描述

将 Node 节点状态置为 0

在这里插入图片描述

唤醒main线程

在这里插入图片描述

此时main线程被t2唤醒

在这里插入图片描述

同理main线程这里也执行置为当前线程锁的线程未null

在这里插入图片描述
然后unLock

在这里插入图片描述
继续唤醒其他线层 这里是t1

在这里插入图片描述
这里还是这个方法,和上面逻辑一样 也是aqs里的

在这里插入图片描述

可以看到t1被唤醒,此时三个线程都被是唤醒状态

在这里插入图片描述

此时将main线程执行到这里

在这里插入图片描述
将t1执行到这里

在这里插入图片描述

将t2执行到这里

在这里插入图片描述

最后
CyclicBarrier 构造,count是计数器, parties 是备份的计数器,barrierCommand 是传进来的任务
在这里插入图片描述

在 await 方法里 dowait 时候可以看到 计数器是 0 了 可以执行这个方法,可以用来做执行结束通知

在这里插入图片描述

最后看重置方法

在这里插入图片描述

重置,以及通知
在这里插入图片描述

最后

实现原理

CyclicBarrier是通过ReentrantLock的"独占锁"和Conditon来实现一组线程的阻塞 唤醒的,而CountDownLatch则是通过AQS的“共享锁”实现

CyclicBarrier执行流程

大概范围的一个执行流程,没有那么细。

  • 1 await() 先进行计数逻辑,然后将线程node节点放入条件队列
  • 2 最后一个await() 计数为 0 时,该线程将条件队列转换为同步队列,然后唤醒下一个同步队列节点,也就是去唤醒下一个线程
  • 3 其他线程被唤醒继续唤醒下一个线程,这里的流程和之前aqs流程是一样的,都是aqs里的逻辑。

都是在lock加锁和解锁里进行操作的

CyclicBarrier与CountDownLatch的区别

  • 1 CountDownLatch的计数器只能使用一次,而CyclicBarrier的计数器可以使用reset() 方法重置。所以CyclicBarrier能处理更为复杂的业务场景,比如如果计算发生错误,可 以重置计数器,并让线程们重新执行一次
  • 2 CyclicBarrier还提供getNumberWaiting(可以获得CyclicBarrier阻塞的线程数量)、 isBroken(用来知道阻塞的线程是否被中断)等方法。
  • 3 CountDownLatch会阻塞主线程,CyclicBarrier不会阻塞主线程,只会阻塞子线程。
  • 4 CountDownLatch和CyclicBarrier都能够实现线程之间的等待,只不过它们侧重点 不同。CountDownLatch一般用于一个或多个线程,等待其他线程执行完任务后,再执 行。CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执 行。
  • 5 CyclicBarrier 还可以提供一个 barrierAction,合并多线程计算结果。
  • 6 CyclicBarrier是通过ReentrantLock的"独占锁"和Conditon来实现一组线程的阻塞 唤醒的,而CountDownLatch则是通过AQS的“共享锁”实现

CountDownLatch

深入理解CountDownLatch