CyclicBarrier源码简析

之前一篇文章讲了一下CountdownLatch,接下来就来讲讲CyclicBarrier,两者有一些相似的地方,也有一些不同,先通过一段demo来了解一下CyclicBarrier的使用

public class CyclicBarrierTest {
    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(3, new Runnable() {
            @Override
            public void run() {
                System.out.println("所有线程执行完毕");
            }
        });

        new Thread(){
            @Override
            public void run() {
                try {
                    System.out.println("线程1执行逻辑");
                    cyclicBarrier.await();
                    System.out.println("线程1继续执行");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }.start();

        new Thread(){
            @Override
            public void run() {
                try {
                    System.out.println("线程2执行逻辑");
                    cyclicBarrier.await();
                    System.out.println("线程2继续执行");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }.start();

        new Thread(){
            @Override
            public void run() {
                try {
                    System.out.println("线程3执行逻辑");
                    cyclicBarrier.await();
                    System.out.println("线程3继续执行");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }
}

输出

线程1执行逻辑
线程2执行逻辑
线程3执行逻辑
所有线程执行完毕
线程3继续执行
线程1继续执行
线程2继续执行

demo中使用到了CyclicBarrier的地方,一共就两处,一个是CyclicBarrier的构造方法,一个是CyclicBarrier的await方法,下面来分别看看两个方法的源码

// parties:在parties个线程都到达屏障之后,这些线程才继续往下执行,可以理解为类似CountDownLatch的倒计时,后面我都用屏障倒计时来描述
// barrierAction:触发屏障之后执行的方法,如果是null的话则不执行任何操作
public CyclicBarrier(int parties, Runnable barrierAction) {
    if (parties <= 0) throw new IllegalArgumentException();
    this.parties = parties;
    this.count = parties;
    this.barrierCommand = barrierAction;
}

构造方法其实就是给几个变量赋值了一下,没什么好说的,接下来看看await方法

public int await() throws InterruptedException, BrokenBarrierException {
    try {
        return dowait(false, 0L);
    } catch (TimeoutException toe) {
        throw new Error(toe); // cannot happen
    }
}

private int dowait(boolean timed, long nanos)
    throws InterruptedException, BrokenBarrierException,
           TimeoutException {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
    	// 每次使用屏障都会代表一个generation实例
    	// 只有在屏障被触发或是重置的时候generation才会改变
        final Generation g = generation;

		// generation中只有一个broken属性,默认为false
        if (g.broken)
            throw new BrokenBarrierException();

        if (Thread.interrupted()) {
        	// 打破屏障,顾名思义,运行到这里的话,所有被阻塞的线程都会继续往下执行
            breakBarrier();
            throw new InterruptedException();
        }

		// 在构造方法中,count的值等于parties,即屏障倒计时,在这里倒计时减1
        int index = --count;
        // 当屏障倒计时为0,即所有的线程都到达屏障之后
        if (index == 0) {  // tripped
            boolean ranAction = false;
            try {
                final Runnable command = barrierCommand;
                if (command != null)
                	// 运行我们要执行的CyclicBarrier构造方法中传入的Runnable方法
                    command.run();
                ranAction = true;
               	// 这个方法中,也会让所有被阻塞的线程继续往下执行
               	// 前面我们提到了generation在屏障被触发时会改变,所以这里new了一个新的generation实例,已经跟上面的实例g不一样了
                nextGeneration();
                return 0;
            } finally {
            	// 如果ranAction这里是false,那么说明我们在执行CyclicBarrier构造方法中传入的Runnable方法时报错了
                if (!ranAction)
                    breakBarrier();
            }
        }

        // loop until tripped, broken, interrupted, or timed out
        for (;;) {
            try {
            	// timed是我们传入的参数,是false
                if (!timed)
                	// private final Condition trip = lock.newCondition();
                	// 这里的trip就是我们最开始加的锁的一个Condition
                	// 在这里的作用就是在上面屏障倒计时不为0的时候阻塞住当前线程,最后实现的效果看起来就像所有线程都在屏障前等待
                	// 通过这里的Condition,我们就能知道上面的breakBarrier和nextGeneration方法中其实就是调用了Condition的signalAll方法来唤醒被阻塞的线程
                    trip.await();
                else if (nanos > 0L)
                	// 传入的nanos大于0的话,则阻塞指定的时间
                    nanos = trip.awaitNanos(nanos);
            } catch (InterruptedException ie) {
            	// 线程被打断执行的操作
                if (g == generation && ! g.broken) {
                    breakBarrier();
                    throw ie;
                } else {
                    // We're about to finish waiting even if we had not
                    // been interrupted, so this interrupt is deemed to
                    // "belong" to subsequent execution.
                    Thread.currentThread().interrupt();
                }
            }

            if (g.broken)
                throw new BrokenBarrierException();

			// 如果之前执行过了nextGeneration方法,这里的g和generation就是不相等的,此时循环结束,方法返回
            if (g != generation)
                return index;

            if (timed && nanos <= 0L) {
                breakBarrier();
                throw new TimeoutException();
            }
        }
    } finally {
   		// 解锁
        lock.unlock();
    }
}

总结一下CyclicBarrier方法里面的主要逻辑,首先构造方法指定了屏障倒计时和我们在CyclicBarrier配置触发后要执行的方法,await方法里面如果屏障倒计时不为0的话,那么线程就会被Condition阻塞。如果程序在运行中出现了异常,那么会执行breakBarrier方法来唤醒所有阻塞的线程;如果最后所有的线程都到达屏障之后,一切顺利,就会执行nextGeneration方法唤醒所有阻塞的线程

源码分析的差不多了,我们来类比一下CountdownLatch,我们可以发现在CountdownLatch的例子中,被阻塞的线程是调用了CountdownLatch的await方法的线程,那些调用countDown方法的线程在执行完countDown方法之后就继续执行了,只有一个线程被阻塞,而其他的线程都继续执行

反观CyclicBarrier,所有调用CyclicBarrier的await方法的线程都被阻塞了,最后当指定数量的线程都调用了await方法之后,大家被一起唤醒,然后继续往下执行

这么说可能有点不太好理解,举个例子吧,CyclicBarrier和CountdownLatch就像幼儿园的小朋友上学,老师代表主线程,小朋友代表多个工作线程。CyclicBarrier就是上学,小朋友们一个个来到幼儿园,如果有人没到,大家都得等着,只有当所有的小朋友都到了之后,老师才能开始给孩子们上课;而CountdownLatch就像放学,当小朋友的家长来了之后,就会把自家的小朋友接走,当最后一个小朋友被接走之后,老师才能走

CountdownLatch和CyclicBarrier下一个不同点就是CyclicBarrier的计数可以复用,我把nextGeneration的源码贴出来

private void nextGeneration() {
    // signal completion of last generation
    trip.signalAll();
    // set up next generation
    // 关键在这里,当屏障被触发之后,本来count已经被递减为了0,但是这里又重置为了parties,所以又能继续使用屏障了
    count = parties;
    generation = new Generation();
}

CountdownLatch的计数器倒计时结束之后就不能继续使用了,而CyclicBarrier再使用完之后,通过nextGeneration和breakBarrier方法又会将计数重置,达到循环利用的效果

### Java `CyclicBarrier` 源码解析 #### 类定义与字段解释 `CyclicBarrier` 是一个同步辅助类,它允许一组线程互相等待直至全部线程均抵达某个共同的屏障点。一旦所有参与方均已就位,则它们会一同继续执行下去[^3]。 ```java public class CyclicBarrier { final ReentrantLock lock = new ReentrantLock(); final Condition trip = lock.newCondition(); private final int parties; private final Runnable barrierCommand; private Generation generation = new Generation(); /** The current number of parties waiting in await(), minus count */ private int count; // Internal helper class to manage state. private static class Generation { boolean broken = false; } } ``` 这段代码展示了 `CyclicBarrier` 的主要成员变量: - `ReentrantLock lock`: 提供独占锁机制用于保护共享资源访问。 - `Condition trip`: 条件队列用来挂起和唤醒等待中的线程。 - `int parties`: 表明有多少个参与者需要达到屏障才能触发释放操作。 - `Runnable barrierCommand`: 当所有线程到达屏障后可以执行的一次性任务。 - `Generation generation`: 记录当前代的状态信息,包括是否已破损。 #### 构造函数初始化 创建一个新的 `CyclicBarrier` 实例时可以选择指定一个动作作为最后一名加入者完成注册后的回调处理程序。 ```java /** * Creates a new {@code CyclicBarrier} that will trip when the given * number of parties (threads) are awaiting upon it, and which executes * the given command when tripped. * * @param parties the number of threads that must invoke {@link #await()} * before the barrier is tripped * @param barrierCommand the command to execute once all threads have become * ready but before any are released; or null if no action should be performed * @throws IllegalArgumentException if {@code parties <= 0} */ public CyclicBarrier(int parties, Runnable barrierCommand) { if (parties <= 0) throw new IllegalArgumentException(); this.parties = parties; this.count = parties; this.barrierCommand = barrierCommand; } // Convenience constructor with default behavior on trip public CyclicBarrier(int parties) { this(parties, null); } ``` #### 主要方法剖析 ##### 方法:`await()` 此方法使调用它的线程进入休眠状态直到其他所有预定数量的线程也完成了各自的 `await()` 调用为止;如果发生异常情况(比如超时或被中断),则整个障碍会被标记为破坏并抛出相应类型的异常给各个等待着的线程。 ```java private int dowait(boolean timed, long nanos) throws InterruptedException, BrokenBarrierException, TimeoutException { final ReentrantLock lock = this.lock; lock.lock(); try { final Generation g = generation; if (g.broken) throw new BrokenBarrierException(); if (Thread.interrupted()) { breakBarrier(); throw new InterruptedException(); } int index = --count; if (index == 0) { // tripped boolean ranAction = false; try { final Runnable command = barrierCommand; if (command != null) command.run(); ranAction = true; nextGeneration(); return 0; } finally { if (!ranAction) breakBarrier(); } } // loop until tripped, broken, interrupted, or timed out for (;;) { try { if (!timed) trip.await(); else if (nanos > 0L) nanos = trip.awaitNanos(nanos); } catch (InterruptedException ie) { if (g == generation && ! g.broken) { breakBarrier(); throw ie; } else { // We're about to finish anyway Thread.currentThread().interrupt(); } } catch (TimeoutException toe) { if (g == generation) { breakBarrier(); throw toe; } else { return index; } } if (g.broken) throw new BrokenBarrierException(); if (g != generation) return index; if (timed && nanos <= 0L) { breakBarrier(); throw new TimeoutException(); } } } finally { lock.unlock(); } } public int await() throws InterruptedException, BrokenBarrierException { try { return dowait(false, 0L); } catch (TimeoutException toe) { throw new Error(toe); // cannot happen } } public int await(long timeout, TimeUnit unit) throws InterruptedException, BrokenBarrierException, TimeoutException { return dowait(true, unit.toNanos(timeout)); } ``` 上述逻辑实现了核心功能,即让线程暂停并在满足条件时重新启动。每当有新线程调用了 `await()` 后计数器减一,当计数值降为零意味着达到了设定好的数目从而激活后续流程控制语句。 ##### 辅助方法:`nextGeneration()` 负责重置内部状态以便下一轮循环使用。 ```java private void nextGeneration() { // signal completion of last generation trip.signalAll(); // set up next generation count = parties; generation = new Generation(); } ``` ##### 错误处理:`breakBarrier()` 当遇到不可恢复错误的情况下调用此方法来通知所有等待中的线程停止工作。 ```java private void breakBarrier() { generation.broken = true; count = parties; trip.signalAll(); } ``` 通过以上介绍可以看出,`CyclicBarrier` 设计精巧地利用了锁机制以及条件变量配合实现了多线程间的协作模式,并提供了灵活易用API接口方便开发者构建复杂的应用场景下的并发结构[^2].
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值