JDK源码系列 CyclicBarrier源码分析

本文深入剖析CyclicBarrier的工作原理,包括其内部结构、核心API解析及应用场景,对比CountDownLatch,强调CyclicBarrier的可重用性和优先级任务执行特性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

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

CyclicBarrier源码分析(基于JDK1.8)

CyclicBarrier整体结构

在这里插入图片描述

一、参数
    /*可重入锁*/
    private final ReentrantLock lock = new ReentrantLock();
    /*等待队列*/
    private final Condition trip = lock.newCondition();
    /*参与等待的线程数量*/
    private final int parties;
    /*当所有线程达到屏障点之后,首先执行的任务*/
    private final Runnable barrierCommand;
    /*实际中仍在等待的线程数,每当有一个线程到达屏障点,count值就会减少一;
    当一次新的运算开始后,count会被重置为parties*/
    private int count;
    /*表示当前屏障的状态,true被破坏,false没有被破坏*/
    private Generation generation = new Generation();
二、构造函数
public CyclicBarrier(int parties, Runnable barrierAction) {
    if (parties <= 0) throw new IllegalArgumentException();
    this.parties = parties;
    this.count = parties;
    this.barrierCommand = barrierAction;
}

public CyclicBarrier(int parties) {
    this(parties, null);
}

参数:

  1. parites:参与相互等待的线程数量
  2. barrierAction:指定所有线程到达屏障点之后,首先执行的操作,该操作由最后一个进入屏障点的线程执行。

这是与CountDownLatch不同的一个点。CyclicBarrier可以指定所有线程到达同步点之后第一个执行的任务(该任务具备了最高的优先级),而CountDownLatch并不可以。

三、CyclicBarrier的核心API
3.1 await
//该方法被调用时表示当前线程已经到达屏障点,当前线程阻塞进入休眠状态
//直到所有线程都到达屏障点,当前线程才会被唤醒
public int await() throws InterruptedException, BrokenBarrierException {
    try {
        return dowait(false, 0L);
    } catch (TimeoutException toe) {
        throw new Error(toe); // cannot happen
    }
}

调用await()方法表示当前线程已经到达了屏障点(同步点),若有其他参与互相等待的线程没有到达屏障点,那么当前线程会被阻塞。

我们来看一下dowait(boolean timed, long nanos)方法的实现。
参数:

  1. timed:true表示当前线程进入timed_waiting状态,false表示当前线程进入waiting状态。
  2. nanos:线程超时等待的时长
private int dowait(boolean timed, long nanos)
        throws InterruptedException, BrokenBarrierException,
        TimeoutException {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        final CyclicBarrier.Generation g = generation;
		/*如果当前屏障被破坏,抛出异常*/
        if (g.broken)
            throw new BrokenBarrierException();
		
		/*如果当前调用await函数的线程被中断,调用breakBarrier函数并抛出异常*/
        if (Thread.interrupted()) {
            breakBarrier();
            throw new InterruptedException();
        }
		/*由于当前线程已经达到屏障点,那么等待到达屏障点的线程数量减少一*/
        int index = --count;
        /*index为0说明所有的线程都到达了屏障点*/
        if (index == 0) {  // tripped
        	/*判断是否有优先执行的任务*/
            boolean ranAction = false;
            try {
                final Runnable command = barrierCommand;
                /*有优先执行的任务,那么由当前调用await()的线程执行该任务*/
                if (command != null)
                    command.run();
                ranAction = true;
                /*执行唤醒全部线程的操作*/
                nextGeneration();
                return 0;
            } finally {
            	/*说明在执行barrierCommand的时候发生了异常*/
                if (!ranAction)
                    breakBarrier();
            }
        }

        /*若所有的线程还未全部到达屏障点*/
        for (;;) {
            try {
            	/*判断阻塞方式,并将当前线程加入Condition队列中*/
                if (!timed)
                    trip.await();
                else if (nanos > 0L)
                    nanos = trip.awaitNanos(nanos);
            } catch (InterruptedException ie) {
            	//若遭遇中断
            	//这个中断异常是从Condition中抛出来的,对应THROW_IN
                if (g == generation && ! g.broken) {
                    breakBarrier();
                    /*将当前异常直接抛出*/
                    throw ie;
                } else {
                    /*
            		 * 	这里两种情况:
            		 *	1. g!=generation,说明被reset过
            		 *	2. g==generation&&g.broken, 说明屏障被破坏了 
					 *	对于第二种情况,当前屏障已经被破坏,说明这次中断是属于这一轮等待到达屏障点的,接下来的操作会直接抛出异常(对应if(g.broken))
					 *	对于第一种情况,由于g!=generation, 说明reset了或者这一轮到达屏障点的任务已经完成,那么接下来的操作会直接退出循环(对应if(g!=generation)),所以这次中断是属于后续执行的操作,我们把中断外抛。
					*/   	
                    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();
    }
}

简单梳理下dowait的流程:

  1. 当前操作需要进行加锁
  2. 判断当前屏障是否被打破或者当前线程被中断,抛出异常。
  3. 若步骤二均未发生,判断当前线程是否是到达屏障点的最后一个线程,如果是,执行BarrierCommand(如果有),唤醒全部在Condition队列上等待的线程,并重置CyclicBarrier的状态,更新generation。
  4. 若当前线程不是最后一个到达屏障点的线程,那么当前线程被挂起,直至被唤醒/中断/超时。

我们来看一下nextGeneration中出现的相关函数。
nextGeneration():唤醒所有Condition队列中的线程,并重置CyclicBarrier的状态

private void nextGeneration() {
   	/*唤醒Condition队列中的所有线程*/
    trip.signalAll();
    /*更新count为初始值*/
    count = parties;
    /*更新generator*/
    generation = new CyclicBarrier.Generation();
}

breakBarrier(): 唤醒所有Condition队列中的线程,并重置CyclicBarrier的成员变量count,并且把generation的broken变量设置为true,表示当前屏障被破坏。

private void breakBarrier() {
    /*表示当前屏障被破坏*/
    generation.broken = true;
    /*更新count为初始值*/
    count = parties;
   	/*唤醒Condition队列中的所有线程*/
    trip.signalAll();
}

上述出现的await、signalAll是AQS的Condition队列的内容,具体可以参考JDK源码分析系列 AQS续篇Condition源码浅析,这里不再赘述。

3.2. 其他API
/*重置CyclicBarrier*/
public void reset() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        breakBarrier();   // break the current generation
        nextGeneration(); // start a new generation
    } finally {
        lock.unlock();
    }
}

/*判断屏障是否被破坏*/
public boolean isBroken() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        return generation.broken;
    } finally {
        lock.unlock();
    }
}

/*获取到达屏障点的线程数量*/
public int getNumberWaiting() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        return parties - count;
    } finally {
        lock.unlock();
    }
}

以上几个函数一眼就能看明白,就不多做分析了。


这便是CyclicBarrier源码的全部分析,其实很简单,没有多大难度,其中关于AQS的知识自行了解。

四、应用

demo

public class CyclicBarrierDemo {
    public static void main(String[] args) throws BrokenBarrierException, InterruptedException {
        final CyclicBarrier barrier = new CyclicBarrier(3);
        new Thread(()->solve(barrier)).start();
        new Thread(()->solve(barrier)).start();
        barrier.await();
        System.out.println(Thread.currentThread().getName() + "线程执行完毕");
    }
    private static void solve(CyclicBarrier barrier) {
        try {
            System.out.println(Thread.currentThread().getName());
            barrier.await();
            System.out.println(Thread.currentThread().getName() + "执行完毕");
        }catch (Exception e){
        }
    }
}
/*
输出:
t1
main
t2
t1线程执行完毕
t2线程执行完毕
main线程执行完毕
*/

通过上述例子我们就可以明白CyclicBarrier的具体功能。
t1执行输出当前线程名操作,到达屏障点后被阻塞。
main线程输出当前线程名操作,到达屏障点后被阻塞。
t2执行输出当前线程名操作并达到屏障点,至此,所有线程达到屏障点,会唤醒t1和main线程执行接下来的操作。

demo2

public class CyclicBarrierDemo {
    public static void main(String[] args) throws BrokenBarrierException, InterruptedException {
        final CyclicBarrier barrier = new CyclicBarrier(3, new FirstTask());
        new Thread(()->solve(barrier), "t1").start();
        new Thread(()->solve(barrier), "t2").start();
        //LockSupport.park(mainThread);
        System.out.println(Thread.currentThread().getName());
        barrier.await();
        System.out.println(Thread.currentThread().getName() + "线程执行完毕");
    }
    private static void solve(CyclicBarrier barrier) {
        try {
            System.out.println(Thread.currentThread().getName());
            barrier.await();
            System.out.println(Thread.currentThread().getName() + "线程执行完毕");
        }catch (Exception e){
        }
    }
	
    static class FirstTask implements Runnable{
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "线程正在执行当前具有最高优先级的任务");
        }
    }
	
	//demo
	static class BankWaterService implements Runnable{
        private CyclicBarrier barrier = new CyclicBarrier(4, this);

        private Executor executor = Executors.newFixedThreadPool(4);

        private ConcurrentHashMap<String, Integer> sheetBankWaterCount = new ConcurrentHashMap<>();

        private void count(){
            for (int i = 0; i < 4; i++) {
                executor.execute(()->{
                    sheetBankWaterCount.put(Thread.currentThread().getName(), 4);
                    try {
                        barrier.await();
                    }catch (Exception e){
                        //todo
                    }
                });
            }
        }
        @Override
        public void run() {
            int result = 0;
            for (Integer i : sheetBankWaterCount.values()){
                result += i;
            }
            System.out.println(result);
        }
    }
    @Test
    public void test(){
        BankWaterService service = new BankWaterService();
        service.count();
    }
	
}
/*
输出:
t1
main
t2
t2线程正在执行当前具有最高优先级的任务
t2线程执行完毕
t1线程执行完毕
main线程执行完毕
*/

我们利用CyclicBarrier来执行BarrierCommand任务,该任务具有最高的优先级,当全部线程到达屏障点后,该任务会优先执行,从源码看该任务由最后一个到达屏障点的线程来进行执行。

以上便是CyclicBarrier的一些实际应用。

五、和CountDownLatch的区别

这个也算一个老生常谈的问题了吧。

JDK源码系列 AQS续篇共享锁源码实现分析中我们已经分析过CountDownLatch,这两个同步辅助器的区别我们来说一下。

  1. 在CountDownLatch中,我们利用到是AQS的共享锁;在CyclicBarrier中,我们利用到的是AQS的独占锁和Condition队列。
  2. CountDownLatch只能够使用一次,而CyclicBarrier是可以循环利用的,通过reset重置来达到循环利用的目的。
  3. CyclicBarrier可以指定任务优先执行,而CountDownLatch是不行的。
  4. CyclicBarrier还提供其他有用的方法,比如 getNumberWaiting方法可以获得CyclicBarrier阻塞的线程数量。isBroken()方法用来判断屏障是否被损坏,中断/超时。
  5. 这个点我想说一下,看过CountDownLatch我们知道,调用countDown()方法的线程之后是可以继续执行的,并不会被阻塞。而在CyClicBarrier中,但线程到达屏障点且当前线程不是最后一个达到屏障点的线程,那么当前线程线程会被阻塞,不能执行接下来的操作,只有等到最后一个到达屏障点的线程唤醒所有的线程,才可以继续接下来的操作。我觉得这里也是应该注意一下的。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值