CyclicBarrier原理

一、简介

CyclicBarrier :字面翻译为 【可重复使用的栅栏】。实现一组线程相互等待,当所有线程都到达某个屏障点后再进行后续的操

二、类图

Generation :当有parties个线程到达barrier之后,generation就会被更新换代。
在这里插入图片描述

三、工作示意图

在这里插入图片描述

四、源码解析

1、构造函数

parties 表示 屏障拦截的线程数; barrierAction 表示 所有线程都到达屏障时 要处理的业务

   public CyclicBarrier(int parties) {
        this(parties, null);
    }
    
   public CyclicBarrier(int parties, Runnable barrierAction) {
        if (parties <= 0) throw new IllegalArgumentException();
        this.parties = parties;
        this.count = parties;
        this.barrierCommand = barrierAction;
    }   

 

2、await()—阻塞当前线程直到所有的线程都到达屏障

(1)种类:2种(带超时时间、 不带超时时间)
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));
    }

 

(2)内部流程:dowait(),主要的屏障方法

如果当前线程不是最后到达的线程,处于等待状态,直到发生以下情况之一:

  • 最后一个线程到达
  • 某个线程等待中断
  • 某个线程等待超时
  • 调用了CyclicBarrier的reset()方法。该方法会将屏障重置为初始状态
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()) {
                /**
                  1、 将损坏状态broken设置为true
                  2、 唤醒其他阻塞在此栅栏上的线程
                **/
                breakBarrier();
                throw new InterruptedException();
            }
            
            //每次内部计数-1
            int index = --count;
            //index==0表示最后一个线程到达屏障
            if (index == 0) {  // tripped
                boolean ranAction = false;
                try {
                    //唤醒所有线程前先执行指定的任务
                    final Runnable command = barrierCommand;
                    if (command != null)
                        command.run();
                    ranAction = true;
                    /**
                      1、唤醒所有线程
                      2、重新设置计数器
                      3、重新设置栅栏代次
                    **/
                    nextGeneration();
                    return 0;
                } finally {
                    //栅栏任务执行失败: broken设为true;唤醒全部等待线程
                    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 {
                        //已换代,即已完成在栅栏上的等待,即在最后一次线程执行signalAll后,并且更新了这个“代”对象。在这个区间,这个线程被中断了.JDK认为任务已经完成了,只需打个标记
                        Thread.currentThread().interrupt();
                    }
                }
             // 当有任何一个线程中断了,就会调用breakBarrier方法
            // 就会唤醒其他的线程,其他线程醒来后,也要抛出异常【因打破栅栏被唤醒则抛异常】
                if (g.broken)
                    throw new BrokenBarrierException();
            // 如果 g == generation,说明还没有换代,那为什么会醒了?
            // 因为一个线程可以使用多个栅栏,当别的栅栏唤醒了这个线程,就会走到这里,所以需要判断是否是当前代。
            // 正是因为这个原因,才需要generation来保证正确。
                //【因为换代而被唤醒则返回计数器的值】
                if (g != generation)
                    return index;
                //【如果线程因为超时而被唤醒则打翻栅栏并抛出异常】
                if (timed && nanos <= 0L) {
                    breakBarrier();
                    throw new TimeoutException();
                }
            }
        } finally { 
            //释放独占锁
            lock.unlock();
        }
    }
breakBarrier()—打破屏障
private void breakBarrier() {
        generation.broken = true;
        count = parties;
        trip.signalAll();
    }
nextGeneration()—更新换代
private void nextGeneration() {
        // signal completion of last generation
        trip.signalAll();
        // set up next generation
        count = parties;
        generation = new Generation();
    }

 

问题:什么情况会线程会抛出 BrokenBarrierException 异常?

  • 其他线程调用reset()
  • 一个线程被中断,调用 breakBarrier()唤醒其他线程时,其他线程抛出

3、reset()—重置栅栏

public void reset() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        breakBarrier();   // break the current generation
        nextGeneration(); // start a new generation
    } finally {
        lock.unlock();
    }

 

五、DEMO—人满发车

//乘客类
public class Passenger implements Runnable {

    private static CyclicBarrier barrier;
    private String name;

    public Passenger(String n, CyclicBarrier b) {
        name = n;
        barrier = b;
    }

    @Override
    public void run() {
        try {
            System.out.println(name + "已经上车");
            barrier.await();
            // 工作线程开始处理,这里用Thread.sleep()来模拟业务处理
            Thread.sleep(100);
            System.out.println("乘车一段时间后," + name + "下车");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}


public class CarMove implements Runnable {
    /**
     * 座位数
     */
    private static final int SEAT = 3;
    /**
     * 每天发车次数 计数
     */
    private static int count = 0;

    /**
     * 人满发车
     */
    @Override
    public void run() {
        count++;
        System.out.println("人满,第" + count + "次发车");

    }

    public static void main(String[] args) {
        CyclicBarrier barrier = new CyclicBarrier(SEAT, new CarMove());
        new Thread(new Passenger("1111", barrier)).start();
        new Thread(new Passenger("2222", barrier)).start();
        new Thread(new Passenger("3333", barrier)).start();
        //这一步是为了观察不同线程组的运行情况
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(new Passenger("4444", barrier)).start();
        new Thread(new Passenger("5555", barrier)).start();
        new Thread(new Passenger("6666", barrier)).start();
    }
}
参考资料:

【深入理解CyclicBarrier原理】https://blog.csdn.net/qq_38293564/article/details/80558157

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
CountDownLatch 是一个计数器,它可以让一个或多个线程等待其他线程执行完毕后再继续执行。它的主要方法是 countDown() 和 await(),其中 countDown() 用于计数减一,await() 用于等待计数器变为0。与 CountDownLatch 相比,CyclicBarrier 的主要区别在于它可以重复使用,而且所有线程必须同时到达栅栏处才能继续执行后续任务。CyclicBarrier 的重要方法是 await(),并且可以通过构造方法传入一个 Runnable,在所有线程都到达栅栏状态时优先执行该动作。CyclicBarrier 内部使用 ReentrantLock 和 Condition 实现等待和唤醒的功能,通过维护一个 count 变量来记录还有多少个线程没有到达栅栏处。 <span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [CountDownLatch与CyclicBarrier](https://blog.csdn.net/weixin_44442186/article/details/123985119)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [JUC多线程:CountDownLatch、CyclicBarrier、Semaphore同步器原理总结](https://blog.csdn.net/a745233700/article/details/120688546)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值