Java并发编程(十)CyclicBarrier

69 阅读1分钟

1 CyclicBarrier的应用

CyclicBarrier可以理解为循环栅栏。

在使用中要求必须规定数量的线程都到达栅栏后,所有在栅栏前等待的线程才能都继续执行。

CyclicBarrier可以循环使用,在本次执行完毕后会将计数器归位。

public static void main(String[] args) throws InterruptedException, BrokenBarrierException {
    // 声明栅栏
    CyclicBarrier barrier = new CyclicBarrier(3,() -> {
        System.out.println("乘客上车了");
    });

    new Thread(() -> {
        System.out.println("第一位乘客就位");
        try {
            barrier.await();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }).start();

    new Thread(() -> {
        System.out.println("第二位乘客就位");
        try {
            barrier.await();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }).start();

    barrier.await();
    System.out.println("发车");

}

2 CyclicBarrier源码解析

2.1 有参构造

CyclicBarrier没有直接使用AQS,而是使用ReentrantLock,底层依旧是AQS提供的支持

public CyclicBarrier(int parties, Runnable barrierAction) {、
    // 健壮性判断!
    if (parties <= 0) throw new IllegalArgumentException();
    // parties是final修饰的,需要在重置时,使用!
    this.parties = parties;
    // count是在执行await用来计数的。
    this.count = parties;
    // 当计数count为0时 ,执行这个Runnnable!再唤醒被阻塞的线程(只在每次count到0时执行一次)
    this.barrierCommand = barrierAction;
}

2.2 await

await方法内无逻辑实现,实际调用的是dowait(boolean timed, long nanos),调用时可以穿入超时时间 如果等待超时,将会抛出TimeoutException,修改CyclicBarrier中的generation,将broken设置为true,在重置之前将无法使用。

不存在超时的情况下: 线程执行await方法,会对count-1,再判断count是否为0

  • 如果不为0,需要添加到AQS中的ConditionObject的Waiter队列中排队,并park当前线程
  • 如果为0,证明线程到齐,需要执行nextGeneration,会先将Waiter队列中的Node全部转移到AQS的队列中,并且有后继节点的,ws设置为-1。没有后继节点设置为0。然后重置count和broker标记。等到unlock执行后,每个线程都会被唤醒。

private int dowait(boolean timed, long nanos) throws InterruptedException, BrokenBarrierException, TimeoutException {
    // CyclicBarrier是基于ReentrantLock-Condition的await和singalAll方法实现的。
    // 相当于synchronized中使用wait和notify
    // 挂起,会释放锁资源。
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        // 里面就是boolean,默认false
        final Generation g = generation;

        // 判断之前栅栏加入线程时,是否有超时、中断等问题,如果有,设置boolean为true,其他线程再进来,直接抛出BrokenBarrierException
        if (g.broken)
            throw new BrokenBarrierException();

        if (Thread.interrupted()) {
            breakBarrier();
            throw new InterruptedException();
        }


        // 对计数器count--
        int index = --count;
        // 如果--完,是0,代表突破栅栏
        if (index == 0) {  
            // 默认false
            boolean ranAction = false;
            try {
                // 如果你用的是2个参数的有参构造,说明你传入了任务,index == 0,先执行CyclicBarrier有参的任务
                final Runnable command = barrierCommand;
                if (command != null)
                    command.run();
                // 设置为true
                ranAction = true;
                nextGeneration();
                return 0;
            } finally {
                if (!ranAction)
                    breakBarrier();
            }
        }

        // --完之后,index不是0,代表还需要等待其他线程
        for (;;) {
            try {
                // 如果没设置超时时间。  await()
                if (!timed)
                    trip.await();
                // 设置了超时时间。  await(1,SECOND)
                else if (nanos > 0L)
                    nanos = trip.awaitNanos(nanos);
            } 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();
    }
}

// count到0,唤醒所有队列里的线程线程
private void nextGeneration() {
    // 这个方法就是将Waiter队列中的节点遍历都扔到AQS的队列中,真正唤醒的时机,是unlock方法
    trip.signalAll();
    // 重置计数器
    count = parties;
    // 重置异常判断
    generation = new Generation();
}