《java.util.concurrent 包源码阅读》21 CyclicBarrier和CountDownLatch

CyclicBarrier是一个用于线程同步的辅助类,它允许一组线程等待彼此,直到所有线程都到达集合点,然后执行某个设定的任务。

现实中有个很好的例子来形容:几个人约定了某个地方集中,然后一起出发去旅行。每个参与的人就是一个线程,CyclicBarrier就是那个集合点,所有人到了之后,就一起出发。

CyclicBarrier的构造函数有两个:

// parties是参与等待的线程的数量,barrierAction是所有线程达到集合点之后要做的动作

public CyclicBarrier(int parties, Runnable barrierAction);



// 达到集合点之后不执行操作的构造函数

public CyclicBarrier(int parties)

 

需要说明的是,CyclicBarrier只是记录线程的数目,CyclicBarrier是不创建任何线程的。线程是通过调用CyclicBarrier的await方法来等待其他线程,如果调用await方法的线程数目达到了预设值,也就是上面构造方法中的parties,CyclicBarrier就会开始执行barrierAction

因此我们来看CyclicBarrier的核心方法dowait,也就是await方法调用的私有方法:

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

            }

           // count就是预设的parties,count减1的值表示还剩余几个

           // 线程没有达到该集合点

           int index = --count;

           // index为0表示所有的线程都已经达到集合点,这时

           // 占用最后一个线程,执行运行设定的任务

           if (index == 0) {

               boolean ranAction = false;

               try {

                   final Runnable command = barrierCommand;

                   if (command != null)

                       command.run();

                   ranAction = true;

                   // 唤醒其他等待的线程,

                   // 更新generation以便下一次运行

                   nextGeneration();

                   return 0;

               } finally {

                   // 如果运行任务时发生异常,设置状态为broken

                   // 并且唤醒其他等待的线程

                   if (!ranAction)

                       breakBarrier();

               }

           }



            // 还有线程没有调用await,进入循环等待直到其他线程

            // 达到集合点或者等待超时

            for (;;) {

                try {

                    // 如果没有设置超时,进行无超时的等待

                    if (!timed)

                        trip.await();

                    // 有超时设置,进行有超时的等待

                    else if (nanos > 0L)

                        nanos = trip.awaitNanos(nanos);

                } catch (InterruptedException ie) {

                    // generation如果没有被更新表示还是当前的运行
// (generation被更新表示集合完毕并且任务成功)
// 在状态没有被设置为broken状态的情况下,遇到线程 // 中断异常表示当前线程等待失败,需要设置为broken // 状态,并且抛出中断异常 if (g == generation && ! g.broken) { breakBarrier(); throw ie; } else { // else对应的条件为:g != generation || g.broken // 表示要么generation已经被更新意味着所有线程已经到达
// 集合点并且任务执行成功,要么就是
是broken状态意味着
// 任务执行失败,无论哪种情况
所有线程已经达到集合点,当
// 前线程要结束等待了,发生了中断异常,需要中断当前
线程
// 表示遇到了中断异常。
Thread.currentThread().interrupt(); } } // 如果发现当前状态为broken,抛出异常 if (g.broken) throw new BrokenBarrierException(); // generation被更新表示所有线程都已经达到集合点 // 并且预设任务已经完成,返回该线程进入等待顺序号 if (g != generation) return index; // 等待超时,设置为broken状态并且抛出超时异常 if (timed && nanos <= 0L) { breakBarrier(); throw new TimeoutException(); } } } finally { lock.unlock(); } }

1. 任何一个线程等待时发生异常,CyclicBarrier都将被设置为broken状态,运行都会失败

2. 每次运行成功之后CyclicBarrier都会清理运行状态,这样CyclicBarrier可以重新使用

3. 对于设置了超时的等待,在发生超时的时候会引起CyclicBarrier的broken

 

说完了CyclicBarrier,再来说说CountDownLatch。

CountDownLatch同样也是一个线程同步的辅助类,同样适用上面的集合点的场景来解释,但是运行模式完全不同。

CyclicBarrier是参与的所有的线程彼此等待,CountDownLatch则不同,CountDownLatch有一个导游线程在等待,每个线程报到一下即可无须等待,等到导游线程发现所有人都已经报到了,就结束了自己的等待。

CountDownLatch的构造方法允许指定参与的线程数量:

public CountDownLatch(int count)


参与线程使用countDown表示报到:

    public void countDown() {

        sync.releaseShared(1);

    }

看到releaseShared很容易使人联想到共享锁,那么试着用共享锁的运行模式来解释就简单得多了:

和信号量的实现类似,CountDownLatch内置一下有限的共享锁。

每个参与线程拥有一把共享锁,调用countDown就等于是释放了自己的共享锁,导游线程await等于一下子要拿回所有的共享锁。那么基于AbstractQueuedSynchronizer类来实现就很简单了:

 

    public void await() throws InterruptedException {

        sync.acquireSharedInterruptibly(1);

    }



    public boolean await(long timeout, TimeUnit unit)

        throws InterruptedException {

        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));

    }

 

await时注意到数量是1,其实这个参数对于CountDownLatch实现的SyncAbstractQueuedSynchronizer的子类)来说是不起作用的,因为需要保证await获取共享锁时必须拿到所有的共享锁,这个参数也就变得没有意义了。看一下Sync的tryAcquireShared方法就明白了:

        protected int tryAcquireShared(int acquires) {

            // 和信号量Semaphore的实现一样,使用state来存储count,

            // 每次释放共享锁就把state减1,state为0表示所有的共享

            // 锁已经被释放。注意:这里的acquires参数不起作用

            return (getState() == 0) ? 1 : -1;

        }

因此Sync的tryReleaseShared就是更新state(每次state减1):

        protected boolean tryReleaseShared(int releases) {

            // 每次state减1,当state为0,返回false表示所有的共享锁都已经释放

            for (;;) {

                int c = getState();

                if (c == 0)

                    return false;

                int nextc = c-1;

                if (compareAndSetState(c, nextc))

                    return nextc == 0;

            }

        }

 

CyclicBarrier和CountDownLatch本质上来说都是多个线程同步的辅助工具,前者可以看成分布式的,后者可以看出是主从式。

 

你可能感兴趣的:(CountDownLatch)