AQS之CyclicBarrier源码解析

本文深入解析了CyclicBarrier和CountDownLatch两种同步工具的原理与应用。CyclicBarrier利用ReentrantLock和Condition实现线程间的循环同步,支持重用;而CountDownLatch通过AQS实现一次性倒计时,用于等待所有线程完成。通过对比,揭示了它们在多线程编程中的不同场景和优势。

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

CyclicBarrier同步器经常被问到,跟CountDownLatch有什么区别。区别还是很大的。
我们从源码看一下:

继承关系及属性

public class CyclicBarrier {
    private static class Generation {
        boolean broken = false;
    }
    private final ReentrantLock lock = new ReentrantLock();
    /** Condition to wait on until tripped */
    private final Condition trip = lock.newCondition();
    /** The number of parties */
    private final int parties;
    /* The command to run when tripped */
    private final Runnable barrierCommand;
    /** The current generation */
    private Generation generation = new Generation();
    private int count;
}

内部有一个可重入锁lock,一个lock的关联条件trip。锁的数量parties。剩余锁计数器count;

构造方法

 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);
 }

可以看到,可以传入锁的数量,同步计数器为paties和一个到达该数量之后的执行方法。

主要方法

public int await() throws InterruptedException, BrokenBarrierException {
try {
        return dowait(false, 0L);
    } catch (TimeoutException toe) {
        throw new Error(toe); // cannot happen
    }
}
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()) {
            breakBarrier();
            throw new InterruptedException();
        }
		//计数器减1
        int index = --count;
        //如果计数器为0,说明该轮已经到达要求数量
        if (index == 0) {  // tripped
            boolean ranAction = false;
            try {
                final Runnable command = barrierCommand;
                //如果有此参数,执行该方法。注意,直接调用run(),同步阻塞
                if (command != null)
                    command.run();
                ranAction = true;
                //进入下一轮,唤醒所有挂起线程,重置计数器
                nextGeneration();
                return 0;
            } finally {
                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 {
                    // We're about to finish waiting even if we had not
                    // been interrupted, so this interrupt is deemed to
                    // "belong" to subsequent execution.
                    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();
    }
}

总结
CyclicBarrier并未直接使用AQS,而是通过ReentratLock和condition实现的。整个流程就很清楚,先传入一个计数器数量,进来一个线程就消耗一个,如果数量不为零,就使用condition.await挂起本线程。如果数量消耗到零,就执行预定方法,并进入下一轮,及重置计数器,唤醒所有之前挂起的方法。
跟CountDownLatch的区别
CountDownLatch是内部类Sync直接继承自AQS,并重写了tryAcquireShare和tryReleaseShare方法。实现了当state不为零时将线程入队挂起,在tryReleaseShare执行减一后,如果state为0则,逐个将入队的线程唤醒的操作。是一次性的操作。

使用示例

public class CyclicBarrierTest {
    static class MyThread extends Thread{
        CyclicBarrier cyclicBarrier;
        public MyThread(CyclicBarrier cyclicBarrier) {
            this.cyclicBarrier=cyclicBarrier;
        }

        @Override
        public void run() {
            try {

                Thread.sleep(5000);
                System.out.println("线程 "+Thread.currentThread().getName()+" 就位,等待其他线程");
                cyclicBarrier.await();
                System.out.println("线程 "+Thread.currentThread().getName()+" 开始执行自己的业务");
                Thread.sleep(2000);
                cyclicBarrier.await();
                System.out.println("线程 "+Thread.currentThread().getName()+" 自己的业务执行完成");
            }catch (InterruptedException e){
                e.printStackTrace();
            }catch (BrokenBarrierException e){
                e.printStackTrace();
            }

        }
    }
    public static void main(String[] args) {
        int count=5;
        CyclicBarrier cyclicBarrier=new CyclicBarrier(count,()->{
            System.out.println("5 threads done");
        });
        for (int i=0;i<count;i++){
            new MyThread(cyclicBarrier).start();
        }
        System.out.println("main thread run");

    }
}

运行结果

main thread run
线程 Thread-1 就位,等待其他线程
线程 Thread-0 就位,等待其他线程
线程 Thread-3 就位,等待其他线程
线程 Thread-2 就位,等待其他线程
线程 Thread-4 就位,等待其他线程
5 threads done
线程 Thread-4 开始执行自己的业务
线程 Thread-1 开始执行自己的业务
线程 Thread-3 开始执行自己的业务
线程 Thread-0 开始执行自己的业务
线程 Thread-2 开始执行自己的业务
5 threads done
线程 Thread-0 自己的业务执行完成
线程 Thread-2 自己的业务执行完成
线程 Thread-1 自己的业务执行完成
线程 Thread-3 自己的业务执行完成
线程 Thread-4 自己的业务执行完成

可以看到五个线程相互等待,到齐后执行预定栅栏操作,然后再进行下一轮的等待。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值