java.util源码解析(五)CyclicBarrier

一.CyclicBarrier的基本使用

在所有子线程运行过程中,设置屏障 cyclicBarrier.await(); 突破屏障后,调用CyclicBarrier中带的一个barrierCommand方法。

public class CyclicBarrierDemo {
    public static class Soldier implements Runnable {
        private String soldier;
        private final CyclicBarrier cyclic;

        public Soldier(CyclicBarrier cyclic, String soldier) {
            this.soldier = soldier;
            this.cyclic = cyclic;
        }

        @Override
        public void run() {
            try {
                //第一个屏障(等待所有子线程一起运行到doWork()之前)
                cyclic.await();
                doWork();
                //第二个屏障(等待所有子线程一起完成doWork()方法)
                cyclic.await();
            } catch (InterruptedException e) {//在等待过程中,线程被中断
                e.printStackTrace();
            } catch (BrokenBarrierException e) {//表示当前CyclicBarrier已经损坏.系统无法等到所有线程到齐了.
                e.printStackTrace();
            }
        }

        void doWork() {
            try {
                Thread.sleep(Math.abs(new Random().nextInt() % 10000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(soldier + ":任务完成");
        }
    }

    public static class BarrierRun implements Runnable {
        boolean flag;
        int N;

        public BarrierRun(boolean flag, int N) {
            this.flag = flag;
            this.N = N;
        }

        @Override
        public void run() {
            if (flag) {
                System.out.println("司令:[士兵" + N + "个,任务完成!]");
            } else {
                System.out.println("司令:[士兵" + N + "个,集合完毕!]");
                flag = true;
            }
        }
    }

    public static void main(String[] args) {
        final int N = 10;
        Thread[] allSoldier = new Thread[N];
        boolean flag = false;
        // 初始化屏障器
        CyclicBarrier cyclic = new CyclicBarrier(N, new BarrierRun(flag, N));
        System.out.println("集合队伍! ");
        for (int i = 0; i < N; i++) {
            System.out.println("士兵" + i + "报道! ");
            allSoldier[i] = new Thread(new Soldier(cyclic, "士兵" + i));
            allSoldier[i].start();
        }
    }
}

二.CyclicBarrier的源码分析

CyclicBarrier的应用,也是等待所有子线程工作完成才会继续屏障器后的代码。听起来和CountDownLatch的作用类似啊,但实际上它们还是有很多区别的:

  • CountDownLatch要显示调用countDown()方法,且一般是用在子线程运行完成后的才执行。
  • 因为CountDownLatch是一次性的,所以不能像CyclicBarrier一样在子线程运行过程中多次设置屏障。
  • CountDownLatch底层用的是共享锁的机制去阻塞调用countDownLatch.await()的那个线程,直到子线程执行完成,再通过await()方法。
  • CyclicBarrier底层是通过排它锁的机制去把所有子线程都放到一个条件队列中去,等到最后一个线程执行 cyclicBarrier.await()方法的时候,就会打破屏障,并唤醒前面所有被阻塞的线程。

总的来说,CountDownLatch可以做到的事,CyclicBarrier也可以做到,反之不一定,CyclicBarrier相对来说更灵活,应用场景更多。

下面来详细分析一下

2.1 初始化分析

CyclicBarrier cyclic = new CyclicBarrier(N, new BarrierRun(flag, N));
先来看一下CyclicBarrier中的类成员变量的含义

public class CyclicBarrier {
    // 表示屏障的一代,即第一个屏障为一代,第二个屏障又为一代
    private static class Generation {
        boolean broken = false;
    }

    /** 进入障碍前必须获取的锁 */
    private final ReentrantLock lock = new ReentrantLock();
    /** 用来表示阻塞的条件 */
    private final Condition trip = lock.newCondition();
    /** 障碍释放前需要等待的线程数量 */
    private final int parties;
    /* 障碍解除后,要执行的方法*/
    private final Runnable barrierCommand;
    /** 当前代 */
    private Generation generation = new Generation();

    /**
     * 表示目前还需要等待多少线程,才能结束当前轮。
     */
    private int count;
    /*
    初始化方法,参数
    parties:屏障可以阻拦多少个子线程,
    barrierAction:屏障突破后要执行的任务
    */
    public CyclicBarrier(int parties, Runnable barrierAction) {
        if (parties <= 0) throw new IllegalArgumentException();
        this.parties = parties;
        this.count = parties;
        this.barrierCommand = barrierAction;
    }
}

初始化主要是指定了可以阻拦的线程个数,以及突破屏障后要执行的任务。

2.2 核心方法 cyclic.await();

通过这个方法就可以看到屏障器是怎么拦截线程的,又是怎么在最后一个线程到达时,打破屏障的。

public int await() throws InterruptedException, BrokenBarrierException {
    try {
    // 实际调用的是这个方法,第一个是是否超时的标志false
    // 第二个是时间 ,这里就是不设置时间,不会超时
        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()) {
            /*
        private void breakBarrier() {
            //这个方法内部会设置 g.broken = true;
            generation.broken = true;
            count = parties;
             // 且唤醒所有阻塞的线程,但是唤醒后的每个线程继续执行,还是会根据 if(g.broken) 抛出异常
            trip.signalAll();
       }
        */
                breakBarrier();
                throw new InterruptedException();
            }
        // 线程正常执行到这里,屏障等待拦截的线程减1
            int index = --count;
            if (index == 0) {    // 如果是最后一个线程到达
                boolean ranAction = false;
                try {
                    final Runnable command = barrierCommand;
                    if (command != null)
                        command.run();  //调用屏障解除后的需要运行的任务
                    ranAction = true;
                    /*
                    里面的逻辑主要是,唤醒条件队列中的线程,并且声称一个新一代的屏障器
                    */
                    nextGeneration();
                    return 0;
                } finally {
                    // 这里是假设command.run()出了异常,那么也调用breakBarrier()方法。
                    if (!ranAction)
                        breakBarrier();
                }
            }

            // loop until tripped, broken, interrupted, or timed out
            for (;;) {
                try {
                    if (!timed)   // 如果没有超时,那么把当前线程放到trip条件队列中去,释放锁。其他线程可以获取锁了。
                        trip.await();
                    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();
        // 被唤醒后,正常返回。 执行完成await()方法
                if (g != generation)
                    return index;
        // 如果超时,也会调用 breakBarrier()
                if (timed && nanos <= 0L) {
                    breakBarrier();
                    throw new TimeoutException();
                }
            }
        } finally {
            lock.unlock();
        }
    }

总结:

CyclicBarrier比较符合人们对屏障拦截器的构想,是通过排他锁,把所有线程全部阻塞在条件队列中,等到最后一个线程到来的时候,就解除屏障,唤醒所有线程继续执行。


CountDownLatch就不同,它是通过共享锁一开始就设置好了里面有多少个共享锁,因此共享锁不释放的话,主线程调用latch.await()就会阻塞。直到最后一个子线程调用latch.countDown()方法,共享锁state==0被释放,并且主线程被唤醒。

猜你喜欢

转载自blog.csdn.net/mooneal/article/details/81016087
今日推荐