首页 > 学院 > 开发设计 > 正文

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

2019-11-14 21:03:17
字体:
来源:转载
供稿:网友
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实现的Sync类(AbstractQueuedSynchronizer的子类)来说是不起作用的,因为需要保证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本质上来说都是多个线程同步的辅助工具,前者可以看成分布式的,后者可以看出是主从式。


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表