• 《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本质上来说都是多个线程同步的辅助工具,前者可以看成分布式的,后者可以看出是主从式。

  • 相关阅读:
    "ERR unknown command 'cluster'"
    shell-url-decode
    mac-ppt-auto-open-recovery-files
    gorm-Duplicate-entry
    mac 终端光标在单词之间移动
    seelog 文件输出格式
    nginx-port-Permission-denied
    浏览器-网络
    浏览器-兼容性
    浏览器-浏览器知识
  • 原文地址:https://www.cnblogs.com/wanly3643/p/3948999.html
Copyright © 2020-2023  润新知