深入理解 CyclicBarrier和CountDownLatch

来源:互联网 发布:扭曲验证码识别 python 编辑:程序博客网 时间:2024/06/08 15:45

最近在深入了解Java并发,看到CyclicBarrier和CountDownLatch 这两个类的时候,觉得这两个类远不是一个只能使用一次,一个可以重复使用这么简单,于是就查了各种资料,下面整理一下自己的一些理解吧。

首先来一些简单的区别:

CountDownLatchCyclicBarrier减计数方式加计数方式计算为0时释放所有等待的线程计数达到指定值时释放所有等待线程计数为0时,无法重置计数达到指定值时,计数置为0重新开始调用countDown()方法计数减一,调用await()方法只进行阻塞,对计数没任何影响调用await()方法计数加1,若加1后的值不等于构造方法的值,则线程阻塞不可重复利用可重复利用后面来个深入的区别,那就是他们各自的await方法底层是不同的,先看CyclicBarrier的源码:

public int await() throws InterruptedException, BrokenBarrierException {        try {            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()) {                breakBarrier();                throw new InterruptedException();            } //后面省略
先调用dowait方法,然后dowait方法里面用的是ReentrantLock类里面的锁,使线程进行同步。

再来看CountDownLatch 的源码:

public void await() throws InterruptedException {        sync.acquireSharedInterruptibly(1);    }

public final void acquireSharedInterruptibly(int arg)            throws InterruptedException {        if (Thread.interrupted())            throw new InterruptedException();        if (tryAcquireShared(arg) < 0)            doAcquireSharedInterruptibly(arg);    }
private void doAcquireSharedInterruptibly(int arg)        throws InterruptedException {        final Node node = addWaiter(Node.SHARED);        boolean failed = true;        try {            for (;;) {                final Node p = node.predecessor();                if (p == head) {                    int r = tryAcquireShared(arg);                    if (r >= 0) {                        setHeadAndPropagate(node, r);                        p.next = null; // help GC                        failed = false;                        return;                    }    //后面省略....
看到后面我发现它好像是用Node类对线程进行同步的,那么Node又是干什么的呢。查看了一下源码:


它是AbstractQueuedSynchronizer的静态内部类,看了一下它上面的注释,发现它它是一个等待队列,是“CLH”的变体的锁队列。科普一下什么是“CLH”,CLH锁即Craig, Landin, and Hagersten (CLH) locks,CLH锁是一个自旋锁,能确保无饥饿性,提供先来先服务的公平性。那什么是自旋锁呢,自旋锁是指当一个线程尝试获取某个锁时,如果该锁已被其他线程占用,就一直循环检测锁是否被释放,而不是进入线程挂起或睡眠状态。自旋锁适用于锁保护的临界区很小的情况,临界区很小的话,锁占用的时间就很短。一个对CLH锁好的解释是:CLH锁也是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程只在本地变量上自旋,它不断轮询前驱的状态,如果发现前驱释放了锁就结束自旋。看到这似乎就明白了Node类内部实现的
锁队列对使用CountDownLatch的线程进行同步。

最后再说一下他们两使用上的差别:

CountDownLatch :一个(或者多个)线程等待另外N个线程完成某个事之后才能执行。比如裁判要等所有运动员都跑完后才能结束比赛。

CyclicBarrier:N个线程互相等待任何一个线程完成之前,所有线程都必须等待。比如一群从不同地方相约去旅游,只有所有人都到某个景点的时候,这群人才能向下一个景点出发。