Java多线程与高并发专题—— CyclicBarrier 和 CountDownLatch 有什么异同?

news2025/4/2 5:38:29

引入

上一篇我们了解CountDownLatch的原理和常见用法,在CountDownLatch的源码注释中,有提到:

另一种典型用法是将一个问题分解为 N 个部分,用一个Runnable描述每个部分,该Runnable执行相应部分的任务并对闭锁进行倒计时操作,然后将所有的Runnable排入一个Executor中。当所有子部分都完成时,协调线程将能够通过await。(当线程必须以这种方式反复进行倒计时操作时,应使用CyclicBarrier代替。)

本文我们就来看一下这个CyclicBarrier和CountDownLatch 的异同。

还是老样子,先初步了解一下CyclicBarrier,先看看CyclicBarrier的源码注释:

A synchronization aid that allows a set of threads to all wait for each other to reach a common barrier point. CyclicBarriers are useful in programs involving a fixed sized party of threads that must occasionally wait for each other. The barrier is called cyclic because it can be re-used after the waiting threads are released.
A CyclicBarrier supports an optional Runnable command that is run once per barrier point, after the last thread in the party arrives, but before any threads are released. This barrier action is useful for updating shared-state before any of the parties continue.
Sample usage: Here is an example of using a barrier in a parallel decomposition design:

class Solver {   
    final int N;   
    final float[][] data;   
    final CyclicBarrier barrier;    
    class Worker implements Runnable {    
        int myRow;     
        Worker(int row) { myRow = row; }     
        public void run() {       
            while (!done()) {       
                processRow(myRow);       
                try {          
                    barrier. await();       
                } catch (InterruptedException ex) {      
                    return;       
                } catch (BrokenBarrierException ex) {    
                    return;        
                }   
            }  
        }  
    } 
    public Solver(float[][] matrix) {  
        data = matrix;  
        N = matrix. length;   
        Runnable barrierAction = new Runnable() { 
            public void run() { mergeRows(...); }
        };   
        barrier = new CyclicBarrier(N, barrierAction);    
        List<Thread> threads = new ArrayList<Thread>(N);    
        for (int i = 0; i < N; i++) {       
            Thread thread = new Thread(new Worker(i));    
            threads. add(thread);      
            thread. start();     
        }      
        // wait until done     
        for (Thread thread : threads)      
            thread. join();   
    } 
}

Here, each worker thread processes a row of the matrix then waits at the barrier until all rows have been processed. When all rows are processed the supplied Runnable barrier action is executed and merges the rows. If the merger determines that a solution has been found then done() will return true and each worker will terminate.
If the barrier action does not rely on the parties being suspended when it is executed, then any of the threads in the party could execute that action when it is released. To facilitate this, each invocation of await returns the arrival index of that thread at the barrier. You can then choose which thread should execute the barrier action, for example:

 

if (barrier. await() == 0) {
    // log the completion of this iteration
}

The CyclicBarrier uses an all-or-none breakage model for failed synchronization attempts: If a thread leaves a barrier point prematurely because of interruption, failure, or timeout, all other threads waiting at that barrier point will also leave abnormally via BrokenBarrierException (or InterruptedException if they too were interrupted at about the same time).
Memory consistency effects: Actions in a thread prior to calling await() happen-before actions that are part of the barrier action, which in turn happen-before actions following a successful return from the corresponding await() in other threads.

翻译:

同步辅助工具,它允许一组线程相互等待,直到全部到达一个公共的屏障点。循环屏障(CyclicBarrier)在涉及固定数量线程组且这些线程偶尔需要相互等待的程序中很有用。之所以称为 “循环”,是因为在等待的线程被释放后,它可以再次使用。
循环屏障支持一个可选的 Runnable 命令,在组内最后一个线程到达后,但在任何线程被释放之前,每次到达屏障点时都会运行此命令。在任何线程组继续执行之前,此屏障操作对于更新共享状态很有用。
示例用法:以下是在并行分解设计中使用屏障的一个示例:

/**
 * Solver 类用于解决矩阵相关的并发计算问题。
 * 该类使用 CyclicBarrier 来同步多个工作线程的执行。
 */
class Solver {
    // 矩阵的行数
    final int N;
    // 存储矩阵数据的二维数组
    final float[][] data;
    // 用于线程同步的 CyclicBarrier
    final CyclicBarrier barrier;

    /**
     * Worker 类是一个内部类,实现了 Runnable 接口,用于处理矩阵的每一行。
     */
    class Worker implements Runnable {
        // 当前工作线程负责处理的行号
        int myRow;

        /**
         * 构造函数,初始化当前工作线程负责处理的行号。
         * @param row 当前工作线程负责处理的行号
         */
        Worker(int row) { 
            myRow = row; 
        }

        /**
         * 实现 Runnable 接口的 run 方法,该方法包含线程的主要逻辑。
         * 线程会持续处理矩阵行,直到完成所有任务。
         */
        public void run() {
            // 循环处理矩阵行,直到 done 方法返回 true
            while (!done()) {
                // 处理当前行的数据
                processRow(myRow);
                try {
                    // 等待所有线程到达屏障点
                    barrier. await();
                } catch (InterruptedException ex) {
                    // 如果线程被中断,则返回
                    return;
                } catch (BrokenBarrierException ex) {
                    // 如果屏障被破坏,则返回
                    return;
                }
            }
        }
    }

    /**
     * Solver 类的构造函数,初始化矩阵数据和线程池。
     * @param matrix 输入的矩阵数据
     */
    public Solver(float[][] matrix) {
        // 初始化矩阵数据
        data = matrix;
        // 获取矩阵的行数
        N = matrix. length;
        // 定义屏障动作,当所有线程到达屏障点时执行
        Runnable barrierAction = new Runnable() {
            /**
             * 实现 Runnable 接口的 run 方法,该方法包含屏障动作的逻辑。
             * 这里调用 mergeRows 方法来合并行数据。
             */
            public void run() { 
                mergeRows(...); 
            }
        };
        // 初始化 CyclicBarrier
        barrier = new CyclicBarrier(N, barrierAction);
        // 创建一个包含 N 个线程的列表
        List<Thread> threads = new ArrayList<Thread>(N);
        // 为矩阵的每一行创建一个工作线程
        for (int i = 0; i < N; i++) {
            // 创建一个新的线程,执行 Worker 任务
            Thread thread = new Thread(new Worker(i));
            // 将线程添加到线程列表中
            threads. add(thread);
            // 启动线程
            thread. start();
        }
        // 等待所有线程完成任务
        for (Thread thread : threads)
            // 等待线程执行完毕
            thread. join();
    }
}

在此示例中,每个工作线程处理矩阵的一行,然后在屏障处等待,直到所有行都处理完毕。当所有行都处理完成后,所提供的 Runnable 屏障动作将被执行,以合并这些行。如果合并操作确定已找到解决方案,那么done()方法将返回true,每个工作线程将终止。
如果屏障动作在执行时不依赖于线程组中的线程处于挂起状态,那么在线程组中的任何线程被释放时,都可以执行该动作。为了便于实现这一点,每次调用await方法都会返回该线程在屏障处的到达索引。然后,你可以选择应由哪个线程执行屏障动作,例如:

/**
 * 检查是否所有线程都已到达屏障点。
 * 当所有线程都到达屏障点时,CyclicBarrier的await()方法将返回0。
 * 如果返回值为0,则表示当前迭代已完成,此时记录该迭代的完成信息。
 */
if (barrier. await() == 0) {
    // log the completion of this iteration
}

循环屏障(CyclicBarrier)对于同步尝试失败采用 “全有或全无” 的中断模型:如果一个线程由于中断、失败或超时而过早地离开屏障点,那么在该屏障点等待的所有其他线程也会通过BrokenBarrierException异常不正常地离开(如果它们几乎在同一时间也被中断,则会抛出InterruptedException异常)。
内存一致性影响:一个线程中调用await()方法之前的操作,先行发生于作为屏障动作一部分的操作,而这些操作又先行发生于其他线程中从相应的await()方法成功返回之后的操作。

具体源码如下:

/**
 * 一个同步辅助类,允许一组线程相互等待,直到所有线程都到达一个共同的屏障点。
 * 该屏障是可循环使用的,因为在等待的线程被释放后,它可以被再次使用。
 */
public class CyclicBarrier {
    /**
     * 每次使用屏障都由一个Generation实例表示。
     * 当屏障被触发或重置时,Generation会发生变化。
     * 由于锁分配给等待线程的方式是不确定的,可能会有多个Generation与使用屏障的线程相关联,
     * 但在任何时候只有一个是活跃的(即{@code count}所适用的那个),其余的要么已损坏,要么已被触发。
     * 如果发生了损坏但没有后续的重置,可能就没有活跃的Generation。
     */
    private static class Generation {
        // 标记屏障是否已损坏
        boolean broken = false;
    }

    /** 用于保护屏障入口的锁 */
    private final ReentrantLock lock = new ReentrantLock();
    /** 线程等待直到屏障被触发的条件 */
    private final Condition trip = lock.newCondition();
    /** 参与屏障的线程数量 */
    private final int parties;
    /* 屏障被触发时要执行的命令 */
    private final Runnable barrierCommand;
    /** 当前的Generation实例 */
    private Generation generation = new Generation();

    /**
     * 仍在等待的线程数量。在每个Generation中,从parties递减到0。
     * 在每个新的Generation或屏障损坏时,会重置为parties。
     */
    private int count;

    /**
     * 更新屏障触发后的状态,并唤醒所有等待的线程。
     * 此方法必须在持有锁的情况下调用。
     */
    private void nextGeneration() {
        // 通知上一个Generation完成
        trip.signalAll();
        // 设置下一个Generation
        count = parties;
        generation = new Generation();
    }

    /**
     * 将当前屏障的Generation标记为损坏,并唤醒所有等待的线程。
     * 此方法必须在持有锁的情况下调用。
     */
    private void breakBarrier() {
        // 标记屏障已损坏
        generation.broken = true;
        // 重置等待线程数量
        count = parties;
        // 唤醒所有等待的线程
        trip.signalAll();
    }

    /**
     * 屏障的主要逻辑,涵盖了各种策略。
     *
     * @param timed  是否设置超时
     * @param nanos  超时时间(纳秒)
     * @return       当前线程的到达索引
     * @throws InterruptedException  如果线程在等待过程中被中断
     * @throws BrokenBarrierException 如果屏障已损坏
     * @throws TimeoutException       如果设置了超时且超时发生
     */
    private int dowait(boolean timed, long nanos)
        throws InterruptedException, BrokenBarrierException,
               TimeoutException {
        // 获取锁
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            // 获取当前的Generation
            final Generation g = generation;

            // 如果屏障已损坏,抛出异常
            if (g.broken)
                throw new BrokenBarrierException();

            // 如果当前线程被中断,打破屏障并抛出异常
            if (Thread.interrupted()) {
                breakBarrier();
                throw new InterruptedException();
            }

            // 减少等待的线程数量
            int index = --count;
            if (index == 0) {  // 所有线程都已到达,触发屏障
                boolean ranAction = false;
                try {
                    // 获取屏障触发时要执行的命令
                    final Runnable command = barrierCommand;
                    if (command != null)
                        command.run();
                    ranAction = true;
                    // 更新到下一个Generation
                    nextGeneration();
                    return 0;
                } finally {
                    // 如果命令执行失败,打破屏障
                    if (!ranAction)
                        breakBarrier();
                }
            }

            // 循环等待,直到屏障被触发、损坏、线程被中断或超时
            for (;;) {
                try {
                    if (!timed)
                        // 无超时等待
                        trip.await();
                    else if (nanos > 0L)
                        // 超时等待
                        nanos = trip.awaitNanos(nanos);
                } catch (InterruptedException ie) {
                    if (g == generation && ! g.broken) {
                        // 如果当前Generation未损坏,打破屏障并抛出异常
                        breakBarrier();
                        throw ie;
                    } else {
                        // 重新设置中断状态
                        Thread.currentThread().interrupt();
                    }
                }

                // 如果屏障已损坏,抛出异常
                if (g.broken)
                    throw new BrokenBarrierException();

                // 如果Generation已更新,返回当前线程的到达索引
                if (g != generation)
                    return index;

                // 如果设置了超时且超时发生,打破屏障并抛出异常
                if (timed && nanos <= 0L) {
                    breakBarrier();
                    throw new TimeoutException();
                }
            }
        } finally {
            // 释放锁
            lock.unlock();
        }
    }

    /**
     * 创建一个新的{@code CyclicBarrier},当指定数量的线程等待时触发,并在屏障触发时执行指定的命令。
     *
     * @param parties       必须调用{@link #await}方法才能触发屏障的线程数量
     * @param barrierAction 屏障触发时要执行的命令,如果没有则为{@code null}
     * @throws IllegalArgumentException 如果{@code parties}小于1
     */
    public CyclicBarrier(int parties, Runnable barrierAction) {
        if (parties <= 0) throw new IllegalArgumentException();
        this.parties = parties;
        this.count = parties;
        this.barrierCommand = barrierAction;
    }

    /**
     * 创建一个新的{@code CyclicBarrier},当指定数量的线程等待时触发,不执行预定义的命令。
     *
     * @param parties 必须调用{@link #await}方法才能触发屏障的线程数量
     * @throws IllegalArgumentException 如果{@code parties}小于1
     */
    public CyclicBarrier(int parties) {
        this(parties, null);
    }

    /**
     * 返回触发此屏障所需的线程数量。
     *
     * @return 触发此屏障所需的线程数量
     */
    public int getParties() {
        return parties;
    }

    /**
     * 等待直到所有{@linkplain #getParties 参与方}都在这个屏障上调用了{@code await}方法。
     *
     * @return 当前线程的到达索引,其中索引{@code getParties() - 1}表示第一个到达,0表示最后一个到达
     * @throws InterruptedException 如果当前线程在等待过程中被中断
     * @throws BrokenBarrierException 如果在当前线程等待时,另一个线程被中断或超时,或者屏障被重置,或者屏障在调用{@code await}时已损坏,或者屏障动作(如果存在)由于异常而失败
     */
    public int await() throws InterruptedException, BrokenBarrierException {
        try {
            return dowait(false, 0L);
        } catch (TimeoutException toe) {
            // 这种情况不会发生
            throw new Error(toe);
        }
    }

    /**
     * 等待直到所有{@linkplain #getParties 参与方}都在这个屏障上调用了{@code await}方法,或者指定的等待时间过去。
     *
     * @param timeout 等待屏障的时间
     * @param unit    超时参数的时间单位
     * @return 当前线程的到达索引,其中索引{@code getParties() - 1}表示第一个到达,0表示最后一个到达
     * @throws InterruptedException 如果当前线程在等待过程中被中断
     * @throws TimeoutException 如果指定的超时时间过去。在这种情况下,屏障将被打破。
     * @throws BrokenBarrierException 如果在当前线程等待时,另一个线程被中断或超时,或者屏障被重置,或者屏障在调用{@code await}时已损坏,或者屏障动作(如果存在)由于异常而失败
     */
    public int await(long timeout, TimeUnit unit)
        throws InterruptedException,
               BrokenBarrierException,
               TimeoutException {
        return dowait(true, unit.toNanos(timeout));
    }

    /**
     * 查询此屏障是否处于损坏状态。
     *
     * @return 如果自构造或上次重置以来,有一个或多个参与方由于中断或超时退出了此屏障,或者屏障动作由于异常而失败,则返回{@code true};否则返回{@code false}。
     */
    public boolean isBroken() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return generation.broken;
        } finally {
            lock.unlock();
        }
    }

    /**
     * 将屏障重置为初始状态。如果有任何参与方当前正在屏障处等待,它们将以{@link BrokenBarrierException}返回。
     * 注意,在由于其他原因发生损坏后进行重置可能会很复杂;线程需要以其他方式重新同步,并选择一个来执行重置。
     * 可能更可取的做法是创建一个新的屏障以供后续使用。
     */
    public void reset() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            // 打破当前的Generation
            breakBarrier();
            // 开始一个新的Generation
            nextGeneration();
        } finally {
            lock.unlock();
        }
    }

    /**
     * 返回当前正在屏障处等待的参与方数量。此方法主要用于调试和断言。
     *
     * @return 当前在{@link #await}方法中被阻塞的参与方数量
     */
    public int getNumberWaiting() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return parties - count;
        } finally {
            lock.unlock();
        }
    }
}

CyclicBarrier的作用

可以看到CyclicBarrier 和 CountDownLatch 确实有一定的相似性,它们都能阻塞一个或者一组线程,直到某种预定的条件达到之后,这些之前在等待的线程才会统一出发,继续向下执行。正因为它们有这个相似点,你可能会认为它们的作用是完全一样的,其实并不是。

CyclicBarrier 可以构造出一个集结点,当某一个线程执行 await() 的时候,它就会到这个集结点开始等待,等待这个栅栏被撤销。直到预定数量的线程都到了这个集结点之后,这个栅栏就会被撤销,之前等待的线程就在此刻统一出发,继续去执行剩下的任务。

举一个生活中的例子。假设我们班级春游去公园里玩,并且会租借三人自行车,每个人都可以骑,但由于这辆自行车是三人的,所以要凑齐三个人才能骑一辆,而且从公园大门走到自行车驿站需要一段时间。那么我们模拟这个场景,写出如下代码:

/**
 * 该类演示了 CyclicBarrier 的使用,CyclicBarrier 是一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点。
 */
public class CyclicBarrierDemo {
    /**
     * 主方法,程序的入口点。
     * 创建一个 CyclicBarrier 实例,该实例在 3 个线程到达屏障点时触发。
     * 创建并启动 6 个线程,每个线程执行一个 Task 任务。
     * 
     * @param args 命令行参数
     */
    public static void main(String[] args) {
        // 创建一个 CyclicBarrier 实例,当 3 个线程调用 await() 方法时,屏障将被打破
        CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
        // 创建并启动 6 个线程,每个线程执行一个 Task 任务
        for (int i = 0; i < 6; i++) {
            // 创建一个新线程并启动,每个线程执行一个 Task 任务
            new Thread(new Task(i + 1, cyclicBarrier)).start();
        }
    }

    /**
     * 静态内部类,实现了 Runnable 接口,表示一个可以在新线程中执行的任务。
     * 每个任务代表一个同学,从大门出发前往自行车驿站,到达后等待其他同学到达。
     */
    static class Task implements Runnable {
        // 同学的编号
        private int id;
        // 用于同步的 CyclicBarrier 实例
        private CyclicBarrier cyclicBarrier;

        /**
         * 构造函数,初始化任务的同学编号和 CyclicBarrier 实例。
         * 
         * @param id            同学的编号
         * @param cyclicBarrier 用于同步的 CyclicBarrier 实例
         */
        public Task(int id, CyclicBarrier cyclicBarrier) {
            // 初始化同学的编号
            this.id = id;
            // 初始化 CyclicBarrier 实例
            this.cyclicBarrier = cyclicBarrier;
        }

        /**
         * 线程执行的任务逻辑。
         * 模拟同学从大门出发前往自行车驿站,到达后等待其他同学到达,然后一起骑车。
         */
        @Override
        public void run() {
            // 输出同学从大门出发的信息
            System.out.println("同学" + id + "现在从大门出发,前往自行车驿站");
            try {
                // 模拟同学前往自行车驿站的时间,随机睡眠 0 到 10 秒
                Thread.sleep((long) (Math.random() * 10000));
                // 输出同学到达自行车驿站并开始等待的信息
                System.out.println("同学" + id + "到了自行车驿站,开始等待其他人到达");
                // 调用 CyclicBarrier 的 await() 方法,等待其他同学到达
                cyclicBarrier.await();
                // 输出同学开始骑车的信息
                System.out.println("同学" + id + "开始骑车");
            } catch (InterruptedException e) {
                // 当线程在睡眠或等待过程中被中断时,打印堆栈跟踪信息
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                // 当 CyclicBarrier 被破坏时,打印堆栈跟踪信息
                e.printStackTrace();
            }
        }
    }
}

在这段代码中可以看到,首先建了一个参数为 3 的 CyclicBarrier,参数为 3 的意思是需要等待 3 个线程到达这个集结点才统一放行;然后我们又在 for 循环中去开启了 6 个线程,每个线程中执行的Runnable 对象就在下方的 Task 类中,直接看到它的 run 方法,它首先会打印出"同学某某现在从大门出发,前往自行车驿站",然后是一个随机时间的睡眠,这就代表着从大门开始步行走到自行车驿站的时间,由于每个同学的步行速度不一样,所以时间用随机值来模拟。

当同学们都到了驿站之后,比如某一个同学到了驿站,首先会打印出“同学某某到了自行车驿站,开始等待其他人到达”的消息,然后去调用 CyclicBarrier 的 await() 方法。一旦它调用了这个方法,它就会陷入等待,直到三个人凑齐,才会继续往下执行,一旦开始继续往下执行,就意味着 3 个同学开始一起骑车了,所以打印出“某某开始骑车”这个语句。

接下来我们运行一下这个程序,结果如下所示:

可以看到 6 个同学纷纷从大门出发走到自行车驿站,因为每个人的速度不一样,所以会有 3 个同学先到自行车驿站,不过在这 3 个先到的同学里面,前面 2 个到的都必须等待第 3 个人到齐之后,才可以开始骑车。后面的同学也一样,由于第一辆车已经被骑走了,第二辆车依然也要等待 3 个人凑齐才能统一发车。

要想实现这件事情,如果你不利用 CyclicBarrier 去做的话,逻辑可能会非常复杂,因为你也不清楚哪个同学先到、哪个后到。而用了 CyclicBarrier 之后,可以非常简洁优雅的实现这个逻辑,这就是它的一个非常典型的应用场景。

执行动作 barrierAction

public CyclicBarrier(int parties, Runnable barrierAction):当 parties 线程到达集结点时,继续往下执行前,会执行这一次这个动作。

接下来我们再介绍一下它的一个额外功能,就是执行动作 barrierAction 功能。CyclicBarrier 还有一个构造函数是传入两个参数的,第一个参数依然是 parties,代表需要几个线程到齐;第二个参数是一个Runnable 对象,它就是我们下面所要介绍的 barrierAction。

当预设数量的线程到达了集结点之后,在出发的时候,便会执行这里所传入的 Runnable 对象,那么假设我们把刚才那个代码的构造函数改成如下这个样子:

具体代码如下: 

public class CyclicBarrierDemo {

    /**
     * 主方法,用于演示CyclicBarrier的使用
     * @param args 命令行参数(未使用)
     */
    public static void main(String[] args) {
        // 创建一个CyclicBarrier实例,当3个线程到达屏障点时,触发屏障动作
        CyclicBarrier cyclicBarrier = new CyclicBarrier(3, () -> {
            System.out.println("凑齐3人了,出发!");
        });

        // 循环创建6个线程,每个线程代表一个同学
        for (int i = 0; i < 6; i++) {
            // 创建一个新的线程,并启动它
            new Thread(new CyclicBarrierDemo.Task(i + 1, cyclicBarrier)).start();
        }
    }
    
    /**
     * 内部静态类Task,实现Runnable接口,代表每个同学的任务
     */
    static class Task implements Runnable {
        // 同学的编号,使用final修饰,确保其值在初始化后不可变
        private final int id;
        // 用于同步的CyclicBarrier实例
        private CyclicBarrier cyclicBarrier;
        
        /**
         * Task类的构造方法
         * @param id 同学的编号
         * @param cyclicBarrier 用于同步的CyclicBarrier实例
         */
        public Task(int id, CyclicBarrier cyclicBarrier) {
            this.id = id;
            this.cyclicBarrier = cyclicBarrier;
        }

        @Override
        public void run() {
            // 输出同学从大门出发的信息
            System.out.println("同学" + id + "现在从大门出发,前往自行车驿站");
            try {
                // 模拟同学到达自行车驿站所需的时间,随机睡眠一段时间
                Thread.sleep((long) (Math.random() * 10000));
                // 输出同学到达自行车驿站并开始等待的信息
                System.out.println("同学" + id + "到了自行车驿站,开始等待其他人到达");
                // 线程到达屏障点,等待其他线程
                cyclicBarrier.await();
                // 输出同学开始骑车的信息
                System.out.println("同学" + id + "开始骑车");
            } catch (InterruptedException e) {
                // 捕获线程中断异常,并输出日志
                System.err.println("同学" + id + "的线程被中断: " + e.getMessage());
            } catch (BrokenBarrierException e) {
                // 捕获屏障损坏异常,并输出日志
                System.err.println("同学" + id + "遇到屏障损坏异常: " + e.getMessage());
            }
        }
    }
}

可以看出,我们传入了第二个参数,它是一个 Runnable 对象,在这里传入了这个 Runnable 之后,这个任务就会在到齐的时候去打印"凑齐3人了,出发!"。上面的代码如果改成这个样子,则执行结果如下所示:

可以看出,三个人凑齐了一组之后,就会打印出“凑齐 3 人了,出发!”这样的语句,该语句恰恰是我们在这边传入 Runnable 所执行的结果。

值得注意的是,这个语句每个周期只打印一次,不是说你有几个线程在等待就打印几次,而是说这个任务只在“开闸”的时候执行一次。

CyclicBarrier 和 CountDownLatch 的异同

下面我们来总结一下 CyclicBarrier 和 CountDownLatch 有什么异同。

相同点:都能阻塞一个或一组线程,直到某个预设的条件达成发生,再统一出发。

但是它们也有很多不同点,具体如下:

  • 作用对象不同:CyclicBarrier 要等固定数量的线程都到达了栅栏位置才能继续执行,而CountDownLatch 只需等待数字倒数到 0,也就是说 CountDownLatch 作用于事件,但CyclicBarrier 作用于线程;CountDownLatch 是在调用了 countDown 方法之后把数字倒数减 1,而 CyclicBarrier 是在某线程开始等待后把计数减 1。
  • 可重用性不同:CountDownLatch 在倒数到 0  并且触发门闩打开后,就不能再次使用了,除非新建一个新的实例;而 CyclicBarrier 可以重复使用,在刚才的代码中也可以看出,每 3 个同学到了之后都能出发,并不需要重新新建实例。CyclicBarrier 还可以随时调用 reset 方法进行重置,如果重置时有线程已经调用了 await 方法并开始等待,那么这些线程则会抛出BrokenBarrierException 异常。
  • 执行动作不同:CyclicBarrier 有执行动作 barrierAction,而 CountDownLatch 没这个功能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2323442.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

leetcode543.二叉树的直径

当前顶点作为拐点时&#xff0c;求左子树加上右子树的高度可以求出该通过该顶点的直径大小&#xff0c;再对该顶点和左右子节点作为拐点时直径大小进行比对&#xff0c;返回最大值 缺点是递归了多次 /*** Definition for a binary tree node.* public class TreeNode {* …

Java EE 进阶:MyBatis案例练习

表白墙 首先我们先准备一下数据库的数据 创建一个信息表 DROP TABLE IF EXISTS message_info;CREATE TABLE message_info (id INT ( 11 ) NOT NULL AUTO_INCREMENT,from VARCHAR ( 127 ) NOT NULL,to VARCHAR ( 127 ) NOT NULL,message VARCHAR ( 256 ) NOT NULL,delete_fla…

路由选型终极对决:直连/静态/动态三大类型+华为华三思科配置差异,一张表彻底讲透!

路由选型终极对决&#xff1a;直连/静态/动态三大类型华为华三思科配置差异&#xff0c;一张表彻底讲透&#xff01; 一、路由&#xff1a;互联网世界的导航系统二、路由类型深度解析三者的本质区别 三、 解密路由表——网络设备的GPS华为&#xff08;Huawei&#xff09;华三&a…

01 相机标定与相机模型介绍

学完本文,您将了解不同相机模型分类、内参意义,及对应的应用代码模型 标定的意义 建模三维世界点投影到二维图像平面的过程。标定输出的是相机模型。 相机模型 相机模型可以解理解为投影模型 +

SICAR标准 汽车焊装生产线触摸屏操作说明

目录 SIMATIC HMI 是西门子工业自动化解决方案的核心组件&#xff0c;支持实时设备监控与交互&#xff0c;文档中展示了其在焊装生产线中以SICAR标准为基础的具体应用&#xff0c;包括车型切换&#xff08;如 AY2/A26&#xff09;、KMC 夹具配置及能源效率分析&#xff0c;适用…

Selenium Web自动化如何快速又准确的定位元素路径,强调一遍是元素路径

如果文章对你有用&#xff0c;请给个赞&#xff01; 匹配的ChromeDriver和浏览器版本是更好完成自动化的基础&#xff0c;可以从这里去下载驱动程序&#xff1a; 最全ChromeDriver下载含win linux mac 最新版本134.0.6998.165 持续更新..._chromedriver 134-CSDN博客 如果你问…

鸿蒙-全屏播放页面(使用相对布局)---持续更新中

最终实现效果图&#xff1a; 实现步骤 创建FullScreenPlay.ets全品播放页面 并将其修改为启动页面。 全屏播放&#xff0c;屏幕必然横过来&#xff0c;所以要将窗口横过来。 编辑 src/main/ets/entryability/EntryAbility.ets 若写在/EntryAbility.ets中&#xff0c;则所有…

全面讲解python的uiautomation包

在常规的模拟鼠标和键盘操作&#xff0c;我们一般使用pyautogui&#xff0c;uiautomation模块不仅能直接支持这些操作&#xff0c;还能通过控件定位方式直接定位到目标控件的位置&#xff0c;而不需要自己去获取对应坐标位置。uiautomation模块不仅支持任意坐标位置截图&#x…

CentOS 7 源码安装libjsoncpp-1.9.5库

安装依赖工具 sudo yum install cmake make gcc cmake 需要升级至 3.8.0 以上可参考&#xff1a;CentOS安装CMakegcc 需要升级至9.0 以上可参考&#xff1a;CentOS 7升级gcc版本 下载源码 wget https://github.com/open-source-parsers/jsoncpp/archive/refs/tags/1.9.5.…

备赛蓝桥杯之第十六届模拟赛第1期职业院校组第五题:回忆画廊

提示&#xff1a;本篇文章仅仅是作者自己目前在备赛蓝桥杯中&#xff0c;自己学习与刷题的学习笔记&#xff0c;写的不好&#xff0c;欢迎大家批评与建议 由于个别题目代码量与题目量偏大&#xff0c;请大家自己去蓝桥杯官网【连接高校和企业 - 蓝桥云课】去寻找原题&#xff0…

Windows下docker使用教程

docker安装 镜像制作镜像加载容器创建更新镜像导出镜像 Windows10安装dockerdocker image制作docker 镜像加载docker 容器创建更新imageimage 导出为.tar文件 #以Windows10 、11为例 linux和Windows区别在于docker安装的程序是哪个操作系统的&#xff0c;后面的内容其实不变 …

Java项目生成接口文档的方案

文章目录 问题&#xff1a;Java项目生成接口文档的方案方案一&#xff1a;Swagger3.0方案二&#xff1a;Apipost两者对比 问题&#xff1a;Java项目生成接口文档的方案 需求 1、需要生成生成时间&#xff0c;作者名称&#xff0c;项目名称&#xff0c;接口名称&#xff0c;请…

案例实践 | 招商局集团以长安链构建“基于DID的航运贸易数据资产目录链”

概览 案例名称 基于DID的航运贸易数据资产目录链 业主单位 招商局集团 上线时间 2024年10月 用户群体 供数用数企业和个人 用户规模 集团内20企业 案例背景 招商局集团深入落实“促进数据高效流通使用、赋能实体经济”精神&#xff0c;深化集团数字化水平&#xff0c…

2025年移动端开发性能优化实践与趋势分析

启动速度优化 本质&#xff1a;缩短首次可见帧渲染时间。 方法&#xff1a; iOS&#xff1a;利用Core ML本地模型轻量化部署&#xff0c;减少云端等待。Android&#xff1a;强制启用SplashScreen API&#xff0c;通过setKeepOnScreenCondition控制动画时长。冷启动需将耗时操…

Docker Compose介绍

基本概念 Docker-Compose是Docker官方的开源项目&#xff0c;负责实现对docker容器集群的快速编排。 可以这么理解&#xff0c;docker compose是docker提出的一个工具软件&#xff0c;可以管理多个docker容器组成一个应用&#xff0c;只需要编写一个YAML格式的配置文件docker…

头歌实践教学平台--【数据库概论】--SQL

一、表结构与完整性约束的修改(ALTER) 1.修改表名 USE TestDb1; alter table your_table rename TO my_table; 2.添加与删除字段 #语句1&#xff1a;删除表orderDetail中的列orderDate alter table orderDetail drop orderDate; #语句2&#xff1a;添加列unitPrice alter t…

算法基础——模拟

目录 1 多项式输出 2.蛇形方阵 3.字符串的展开 模拟&#xff0c;顾名思义&#xff0c;就是题⽬让你做什么你就做什么&#xff0c;考察的是将思路转化成代码的代码能⼒。这类题⼀般较为简单&#xff0c;属于竞赛⾥⾯的签到题&#xff08;但是&#xff0c;万事⽆绝对&#xff…

【第30节】MFC编程:ListCtrl控件和TreeCtrl控件

目录 引言 一、高级控件ListCtrl 二、高级控件TreeCtrl 三、Shell控件 四、CImageList 五、综合代码示例 引言 在MFC编程里&#xff0c;高级控件能大幅提升应用程序的交互性与功能性。接下来&#xff0c;咱们会详细讲讲ListCtrl和TreeCtrl这两个高级控件。不仅会介绍它们…

JavaScript 手写 call、apply、bind 和 new

1. 手写 call 方法 核心思路&#xff1a;改变函数的 this 指向并立即执行&#xff0c;通过将函数临时挂载到目标对象上调用。 Function.prototype.myCall function (context, ...args) {// 如果 context 为 null 或 undefined&#xff0c;则默认为 windowcontext context |…

计算机网络基础:量子通信技术在网络中的应用前景

计算机网络基础:量子通信技术在网络中的应用前景 一、前言二、量子通信技术基础2.1 量子通信的基本概念2.2 量子通信的主要原理2.2.1 量子密钥分发(QKD)原理2.2.2 量子隐形传态原理三、量子通信技术的特点3.1 绝对安全性3.2 超高通信速率潜力3.3 抗干扰能力强四、量子通信技…