同步工具之CyclicBarrier循环栅栏

CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。CyclicBarrier默认的构造方法是CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞。


CyclicBarrier简介

CyclicBarrier也是一种多线程并发控制的实用工具,和CountDownLatch一样具有等待计数的功能,但是相比于CountDownLatch功能更加强大。

CyclicBarrier的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行。

为了理解CyclicBarrier,这里举一个通俗的例子。开运动会时,会有跑步这一项运动,我们来模拟下运动员入场时的情况,假设有6条跑道,在比赛开始时,就需要6个运动员在比赛开始的时候都站在起点了,裁判员吹哨后才能开始跑步。跑道起点就相当于“barrier”,是临界点,而这6个运动员就类比成线程的话,就是这6个线程都必须到达指定点了,意味着凑齐了一波,然后才能继续执行,否则每个线程都得阻塞等待,直至凑齐一波即可。cyclic是循环的意思,也就是说CyclicBarrier当多个线程凑齐了一波之后,仍然有效,可以继续凑齐下一波。CyclicBarrier的执行示意图如下:

在这里插入图片描述

当多个线程都达到了指定点后,才能继续往下继续执行。这就有点像报数的感觉,假设6个线程就相当于6个运动员,到赛道起点时会报数进行统计,如果刚好是6的话,这一波就凑齐了,才能往下执行。CyclicBarrier在使用一次后,下面依然有效,可以继续当做计数器使用,这是与CountDownLatch的区别之一。这里的6个线程,也就是计数器的初始值6,是通过CyclicBarrier的构造方法传入的。


CyclicBarrier方法

CyclicBarrier方法
在这里插入图片描述
下面来看下CyclicBarrier的主要方法

//等到所有的线程都到达指定的临界点
await() throws InterruptedException, BrokenBarrierException 

//与上面的await方法功能基本一致,只不过这里有超时限制,阻塞等待直至到达超时时间为止
await(long timeout, TimeUnit unit) throws InterruptedException, 
BrokenBarrierException, TimeoutException 

//获取当前有多少个线程阻塞等待在临界点上
int getNumberWaiting()

//用于查询阻塞等待的线程是否被中断
boolean isBroken()

//将屏障重置为初始状态。如果当前有线程正在临界点等待的话,将抛出BrokenBarrierException。

void reset()

CyclicBarrier实例

package cn.wideth.util;

import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MyCyclicBarrier {

    //指定必须有6个运动员到达才行
    private static CyclicBarrier barrier = new CyclicBarrier(6, () ->
            System.out.println("所有运动员入场,裁判员一声令下!!!!!")
    );

    public static void main(String[] args) {

        System.out.println("运动员进场中...");

        ExecutorService service = Executors.newFixedThreadPool(6);
        for (int i = 0; i < 6; i++) {
            service.execute(() -> {
                try {
                    System.out.println(Thread.currentThread().getName() + " 运动员,进场");
                    barrier.await();
                    System.out.println(Thread.currentThread().getName() + "  运动员出发");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
    }

}

运行结果

在这里插入图片描述
代码分析

当某个线程调用了await方法之后,就会进入等待状态,并将计数器-1,直到所有线程调用await方法使计数器为0,才可以继续执行,由于计数器可以重复使用,所以我们又叫它循环屏障。

CyclicBarrier默认的构造方法是CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞。


CyclicBarrier和CountDownLatch的区别

  1. CountDownLatch的计数器只能使用一次。而CyclicBarrier的计数器可以使用reset()方法重置。所以CyclicBarrier能处理更为复杂的业务场景,比如如果计算发生错误,可以重置计数器,并让线程们重新执行一次。
  2. CountDownLatch主要用于实现一个或n个线程需要等待其他线程完成某项操作之后,才能继续往下执行,描述的是一个或n个线程等待其他线程的关系,而CyclicBarrier是多个线程相互等待,知道满足条件以后再一起往下执行。描述的是多个线程相互等待的场景
  3. CyclicBarrier还提供其他有用的方法,比如getNumberWaiting方法可以获得CyclicBarrier阻塞的线程数量。isBroken方法用来知道阻塞的线程是否被中断。

CyclicBarrier类的源码分析

内部实际上通过ReentrantLock控制线程以及通过ReentrantLock得到Condition接口,通过condition控制线程。CyclicBarrier的使用则是通过new CyclicBarrier(10)表示可以拦截10个线程,然后在多线程中通过await()方法,会通过Condition接口让线程阻塞,当很多线程调用await()使CyclicBarrier实例中的count=0时则会被condition通过notifyAll唤醒所有线程,然后拦截器重新生成一个Generation ,并count被重新赋值为parties然后CyclicBarrier就可以被重复利用了。

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;


public class CyclicBarrier {

    private final ReentrantLock lock = new ReentrantLock(); // 底层是可重入的非公平锁
    private final Condition trip = lock.newCondition(); // condition接口
    private final int parties; // 总数,构造时传入,目的时做屏障器的计数(被设计成常量也是出于此,目的就是为了重复利用屏障器)
    private int count; // 计数(count则会根据调用await减一,为0后会被重新赋值为parties)
    private final Runnable barrierCommand; // runable的线程任务
    private Generation generation = new Generation(); // 存在目的是循环使用CyclicBarrier,相当于每一次循环这个对象就会变具体看 nextGeneration()

    /**
     * 构造方法,传入屏障器计数和Runnable接口的任务
     */
    public CyclicBarrier(int parties, Runnable barrierAction) {
        if (parties <= 0) throw new IllegalArgumentException(); // 如果屏障器的计数非正数则不合法,抛异常
        this.parties = parties; // 总数
        this.count = parties;   // 相当于信号量,每调用一次await就会被减1,为0重新赋值为parties
        this.barrierCommand = barrierAction;// 当计数为0时会执行这个Runnable任务
    }

    /**
     * 构造方法
     */
    public CyclicBarrier(int parties) {
        this(parties, null);
    }

    /*静态内部类*/
    private static class Generation {
        Generation() {}                 // 构造方法
        boolean broken;                 // 初始化会是false,如果为true表示CyclicBarrier不再进行循环
    }


    /**
     * 唤醒阻塞线程,进行下一个拦截循环
     */
    private void nextGeneration() {
        trip.signalAll(); // trip时可重入锁得到的condition接口,调用这个方法会唤醒所有正在等待的线程
        count = parties;  // 唤醒了所有的线程,因此count计数会重新赋值为parties,相当于重用屏障器
        generation = new Generation(); // 生成新的
    }

    /**
     * 暂停拦截器作用
     */
    private void breakBarrier() {
        generation.broken = true;   // 设置为false
        count = parties;            // count重新赋值为parties,再次使用时就是新的循环
        trip.signalAll();           // 唤醒所有被阻塞的线程
    }

    /**
     * 拦截线程的方法,被await调用传入的是false,0
     */
    private int dowait(boolean timed, long nanos) throws InterruptedException, BrokenBarrierException, TimeoutException {
        final ReentrantLock lock = this.lock; //可重入锁
        lock.lock(); // 加锁
        try {
            final Generation g = generation; // 得到当前拦截器的用于标识的Generation

            if (g.broken) // 如果为true,说明暂停了拦截(调用了breakBarrier方法)导致
                throw new BrokenBarrierException();//因为已经被叫停拦截器了所以抛异常

            if (Thread.interrupted()) { // 当前线程是否被中断
                breakBarrier(); // 暂停拦截器
                throw new InterruptedException(); // 抛出被中断异常
            }
            // 计算剩余值
            int index = --count; // 如果执行到这里,说明拦截器正常使用,因此计数减一,相当于使用了一个信号一个意思,而parties则是总信号量
            // 如果剩余值为0执行任务
            if (index == 0) {  // 当前线程使用了一个计数后(相当于使用了一个信号量,判断信号量是否被使用完了)如果为0则进入
                Runnable command = barrierCommand; // 任务
                if (command != null) { // 如果任务不为空
                    try {
                        command.run(); // 执行任务
                    } catch (Throwable ex) {
                        breakBarrier(); //异常则暂停拦截器并且下面throw异常
                        throw ex;
                    }
                }
                nextGeneration(); // 生成下一个循环拦截
                return 0;//
            }

            // 如果剩余值不为0,则将当前线程阻塞
            /**
             * 自旋方式降低cpu上下文切换的可能
             */
            for (;;) {
                //下面的try似乎不满足条件所以看后面
                try {
                    if (!timed) // 默认传入的是false,!false=true,所以会让线程阻塞
                        trip.await(); // 通过condition接口阻塞当前线程
                    else if (nanos > 0L)// 默认是0所以不大于
                        nanos = trip.awaitNanos(nanos); // 阻塞nanos纳秒,实际默认值是0纳秒
                } catch (InterruptedException ie) {
                    if (g == generation && ! g.broken) { // 如果等待过程中被修改了则需要抛异常
                        breakBarrier(); // 赞赏他使用拦截器并抛异常
                        throw ie;
                    } else {
                        Thread.currentThread().interrupt(); // 中断当前线程
                    }
                }

                if (g.broken)  // 不执行
                    throw new BrokenBarrierException();

                if (g != generation) // 不会执行
                    return index;

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

    // 获得parties的值(拦截器能够拦截线程的总数)
    public int getParties() {
        return parties;
    }

    /**
     * 拦截线程,等待通知.最常用方法,本质调用dowait方法
     */
    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));
    }

    /**
     * 返回generation.broken的值,目的是根据true/false判断是否拦截器是否继续使用,false则暂停使用
     */
    public boolean isBroken() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return generation.broken;
        } finally {
            lock.unlock();
        }
    }

    /**
     * 重置拦截器
     */
    public void reset() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            breakBarrier();   // 暂停拦截器
            nextGeneration(); // 生成下一个循环
        } finally {
            lock.unlock();
        }
    }

    /**
     * 获取正在等待的线程数
     */
    public int getNumberWaiting() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return parties - count; // 相当于总信号量-剩余信号量=正在执行的线程数(在CyclicBarrier则是自旋的线程数)
        } finally {
            lock.unlock();
        }
    }
}


本文小结

本文详细介绍了CyclicBarrier循环栅栏的基本概念,应用场景,常用方法以及源码分析。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
CyclicBarrierJava 中的一个同步工具类,它允许一组线程互相等待,直到所有线程都到达某个屏障点,然后再一起继续执行。CyclicBarrier 可以循环使用,因此称之为循环屏障。 下面是 CyclicBarrier 工具类的基本使用示例: ```java import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; public class CyclicBarrierDemo { public static void main(String[] args) { // 创建一个 CyclicBarrier 实例,指定等待线程数和屏障动作 CyclicBarrier cyclicBarrier = new CyclicBarrier(3, () -> { System.out.println("所有线程已到达屏障点,开始执行屏障动作"); }); // 创建 3 个线程,模拟多个线程同时到达屏障点 for (int i = 0; i < 3; i++) { new Thread(() -> { try { System.out.println(Thread.currentThread().getName() + " 已到达屏障点,等待其他线程"); cyclicBarrier.await(); // 等待其他线程到达屏障点 System.out.println(Thread.currentThread().getName() + " 继续执行"); } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); } }).start(); } } } ``` 上述代码中,我们创建了一个 CyclicBarrier 实例,指定等待线程数和屏障动作。然后创建了 3 个线程,模拟多个线程同时到达屏障点,每个线程到达屏障点后等待其他线程,直到所有线程到达屏障点,才会继续执行。 在 CyclicBarrier 的构造函数中,我们指定了等待线程数为 3,也就是说,只有当 3 个线程都到达屏障点时,才会执行屏障动作。屏障动作是一个 Runnable 对象,它会在所有线程到达屏障点后执行一次。 在每个线程执行的代码中,我们调用了 CyclicBarrier 的 await() 方法,等待其他线程到达屏障点。当所有线程都到达屏障点后,await() 方法会返回,线程继续执行。 需要注意的是,CyclicBarrier 的 await() 方法可能会抛出 InterruptedException 和 BrokenBarrierException 异常,因此需要在 catch 块中处理这两个异常。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值