Loading

[Java并发]CyclicBarrier

CyclicBarrier 循环栅栏

CyclicBarrier 字面意思回环栅栏(循环屏障),通过它可以实现让一组线程等待至某个状态(屏障点)之后再全部同时执行。叫做回环是因为当所有等待线程都被释放以后,CyclicBarrier可以被重用。

CyclicBarrier的使用

构造方法

 // parties表示屏障拦截的线程数量,每个线程调用 await 方法告诉 CyclicBarrier 我已经到达了屏障,然后当前线程被阻塞。
 public CyclicBarrier(int parties)
 // 用于在线程到达屏障时,优先执行 barrierAction,方便处理更复杂的业务场景(该线程的执行时机是在到达屏障之后再执行)

重要方法

//屏障 指定数量的线程全部调用await()方法时,这些线程不再阻塞
// BrokenBarrierException 表示栅栏已经被破坏,破坏的原因可能是其中一个线程 await() 时被中断或者超时
public int await() throws InterruptedException, BrokenBarrierException
public int await(long timeout, TimeUnit unit) throws InterruptedException, BrokenBarrierException, TimeoutException
//循环  通过reset()方法可以进行重置

CyclicBarrier有两个构造函数

public CyclicBarrier(int parties)
public CyclicBarrier(int parties, Runnable barrierAction)

第一个参数,其参数表示屏障拦截的线程数量,每个线程调用await方法告诉CyclicBarrier已经到达屏障位置,线程被阻塞。

第二个参数,表示线程都处于barrier时,一起执行之前,其中barrierAction任务会在所有线程到达屏障后执行。

让线程处于barrier状态的方法await()

public int await()
public int await(long timeout, TimeUnit unit)

第一个默认方法,表示要等到所有的线程都处于barrier状态,才一起执行。
第二个方法,指定了等待的时间,当所有线程没有都处于barrier状态,又到了指定的时间,所在的线程就继续执行了。

其它的一些方法

//获取当前有多少个线程阻塞等待在临界点上
int getNumberWaiting()
//用于查询阻塞等待的线程是否被中断
boolean isBroken()

CyclicBarrier是通过维护计数器来实现的。线程执行 await() 方法之后计数器会减 1,并进行等待,直到计数器为 0,所有调用 await() 方法而在等待的线程才能继续执行。

public CyclicBarrier(int parties, Runnable barrierAction)方法解释

CyclicBarrier 是 Java 中的一个同步辅助类,它允许一组线程互相等待,直到它们都达到一个共同的屏障点。这个类的一个常见用途是实现多线程并行任务的协调。

构造方法 public CyclicBarrier(int parties, Runnable barrierAction) 创建了一个 CyclicBarrier 实例,参数如下:

  • parties:这是一个整数,表示参与同步的线程数量。当这些线程中的每一个都调用了 await() 方法,屏障就会被解除,所有被阻塞的线程将继续执行。

  • barrierAction:这是一个 Runnable 对象,表示当最后一个线程到达屏障时执行的操作。这是在所有线程都调用了 await() 方法并且屏障被解除后,由最后一个调用 await() 方法的线程执行的动作。可以用来执行一些在所有线程都到达屏障点之后需要进行的工作,例如合并结果、更新状态等。

使用示例

以下是一个简单的使用示例,展示了如何使用 CyclicBarrier 来同步一组线程,并在所有线程都到达屏障后执行一个动作:

import java.util.concurrent.*;

public class Main {
    public static void main(String[] args) {
        final int parties = 3;
        Runnable barrierAction = () -> System.out.println(Thread.currentThread().getName() + "All parties have arrived at the barrier, let's play!");

        CyclicBarrier barrier = new CyclicBarrier(parties, barrierAction);

        for (int i = 0; i < parties; i++) {
            new Thread(new Task(barrier)).start();
        }
    }
}

class Task implements Runnable {
    private CyclicBarrier barrier;

    public Task(CyclicBarrier barrier) {
        this.barrier = barrier;
    }

    @Override
    public void run() {
        try {
            System.out.println(Thread.currentThread().getName() + " is waiting at the barrier.");
            barrier.await();
            System.out.println(Thread.currentThread().getName() + " has crossed the barrier.");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
Thread-0 is waiting at the barrier.
Thread-1 is waiting at the barrier.
Thread-2 is waiting at the barrier.
Thread-2All parties have arrived at the barrier, let's play!
Thread-2 has crossed the barrier.
Thread-1 has crossed the barrier.
Thread-0 has crossed the barrier.

详细解释

  1. 初始化 CyclicBarrier

    • parties 设置为 3,意味着有三个线程需要同步。
    • barrierAction 是一个简单的输出语句,当所有线程到达屏障时执行。
  2. 启动线程

    • 循环创建并启动三个线程,每个线程都运行 Task 对象。
  3. 线程执行 await() 方法

    • 每个线程调用 await() 方法并等待其他线程到达屏障。
    • 当第三个线程调用 await() 方法时,屏障被解除,所有线程继续执行。
    • barrierAction 由最后一个到达屏障的线程执行。

注意事项

  1. 屏障重用

    • CyclicBarrier 可以重用,即当所有线程都通过屏障后,可以重新使用同一个 CyclicBarrier 实例来同步下一组线程。
  2. 异常处理

    • 在调用 await() 方法时,可能会抛出 BrokenBarrierExceptionInterruptedException,需要在代码中进行处理。

通过 CyclicBarrier,我们可以轻松地实现多线程之间的协调和同步,确保所有线程都在特定的同步点汇合并执行相应的操作。

CyclicBarrier 应用场景

利用 CyclicBarrier 可以用于多线程计算数据,最后合并计算结果的场景。
利用 CyclicBarrier的计数器能够重置,屏障可以重复使用的特性,可以支持类似“人满发车”的场景

模拟合并计算场景

利用 CyclicBarrier 可以用于多线程计算数据,最后合并计算结果的场景。

import java.util.Set;
import java.util.concurrent.*;

public class Main {
    public static void main(String[] args) {
        //保存每个学生的平均成绩
        ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<String, Integer>();

        ExecutorService threadPool = Executors.newFixedThreadPool(3);

        CyclicBarrier cb = new CyclicBarrier(3, () -> {
            int result = 0;
            Set<String> set = map.keySet();
            for (String s : set) {
                result += map.get(s);
            }
            System.out.println(Thread.currentThread().getName() + "同学负责计算三人平均成绩 :" + (result / 3) + "分");
        });


        for (int i = 0; i < 3; i++) {
            threadPool.execute(new Runnable() {

                @Override
                public void run() {
                    //获取学生平均成绩
                    int score = (int) (Math.random() * 40 + 60);
                    map.put(Thread.currentThread().getName(), score);
                    System.out.println(Thread.currentThread().getName()
                            + "同学的平均成绩为:" + score);
                    try {
                        //执行完运行await(),等待所有学生平均成绩都计算完毕
                        cb.await();
                    } catch (InterruptedException | BrokenBarrierException e) {
                        e.printStackTrace();
                    }
                }

            });
        }
    }
}
pool-1-thread-1同学的平均成绩为:82
pool-1-thread-3同学的平均成绩为:96
pool-1-thread-2同学的平均成绩为:60
pool-1-thread-2同学负责计算三人平均成绩 :79

模拟“人满发车”的场景

利用CyclicBarrier的计数器能够重置,屏障可以重复使用的特性,可以支持类似“人满发车”的场景

import java.util.Set;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;


public class Main {
    public static void main(String[] args) {
        AtomicInteger counter = new AtomicInteger();

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                5, 5, 1000, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(100),
                (r) -> new Thread(r, counter.addAndGet(1) + " 号 "),
                new ThreadPoolExecutor.AbortPolicy());

        CyclicBarrier cyclicBarrier = new CyclicBarrier(5,
                () -> {
                    System.out.println(Thread.currentThread().getName() + " 充当裁判,发令");
                });

        for (int i = 0; i < 15; i++) {
            threadPoolExecutor.submit(new Runner(cyclicBarrier));
        }
    }

    static class Runner extends Thread {
        private CyclicBarrier cyclicBarrier;

        public Runner(CyclicBarrier cyclicBarrier) {
            this.cyclicBarrier = cyclicBarrier;
        }

        @Override
        public void run() {
            try {
                System.out.println(Thread.currentThread().getName() + " 选手已就位");
                cyclicBarrier.await();
                System.out.println(Thread.currentThread().getName() + " 开始执行");

            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }
    }

}

输出结果:

2 号  选手已就位
1 号  选手已就位
3 号  选手已就位
4 号  选手已就位
5 号  选手已就位
5 号  充当裁判,发令
5 号  开始执行
2 号  开始执行
4 号  开始执行
1 号  开始执行
3 号  开始执行
3 号  选手已就位
4 号  选手已就位
5 号  选手已就位
1 号  选手已就位
2 号  选手已就位
2 号  充当裁判,发令
2 号  开始执行
3 号  开始执行
4 号  开始执行
5 号  开始执行
1 号  开始执行
2 号  选手已就位
1 号  选手已就位
5 号  选手已就位
4 号  选手已就位
3 号  选手已就位
3 号  充当裁判,发令
3 号  开始执行
1 号  开始执行
5 号  开始执行
4 号  开始执行
2 号  开始执行

CyclicBarrier 源码分析

CyclicBarrier 流程
主要是的流程:

获取锁 如果 count != 0 就进入阻塞;
进入阻塞之前,首先需要进入条件队列,然后释放锁,最后阻塞;
如果 count != 0 会进行一个唤醒,将所有的条件队列中的节点转换为阻塞队列;
被唤醒过后会进行锁的获取,如果锁获取失败,会进入 lock 的阻塞队列;
如果锁获取成功,进行锁的释放,以及唤醒,同步队列中的线程。

下面是一个简单的流程图:

image

下面是具体的一些代码调用的流程。

image

几个常见的问题?

  1. 一组线程在触发屏障之前互相等待,最后一个线程到达屏障后唤醒逻辑是如何实现的. 唤醒的过程是通过调用 java.util.concurrent.locks.Condition#signalAll唤醒条件队列上的所有节点。

  2. 删栏循环使用是如何实现的? 实际上一个互斥锁 ReentrantLock 的条件队列和阻塞队列的转换。

  3. 条件队列到同步队列的转换实现逻辑 ? 转换过程中,首先会先将条件队列中所有的阻塞线程唤醒,然后会去获取 lock 如果获取失败,就进入同步队列。

CyclicBarrier 与 CountDownLatch的区别

CountDownLatch的计数器只能使用一次,而CyclicBarrier的计数器可以使用reset()方法重置(可以不显示调用reset,默认也会调用reset)。所以CyclicBarrier能处理更为复杂的业务场景,比如如果计算发生错误,可以重置计数器,并让线程们重新执行一次。

CyclicBarrier还提供getNumberWaiting(可以获得CyclicBarrier阻塞的线程数量)、isBroken(用来知道阻塞的线程是否被中断)等方法。

CountDownLatch会阻塞主线程,CyclicBarrier不会阻塞主线程,只会阻塞子线程。

CountDownLatch和CyclicBarrier都能够实现线程之间的等待,只不过它们侧重点不同。CountDownLatch一般用于一个或多个线程,等待其他线程执行完任务后,再执行。CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行。
CyclicBarrier 还可以提供一个 barrierAction,合并多线程计算结果。

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

posted @   Duancf  阅读(80)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
点击右上角即可分享
微信分享提示