深入理解 CyclicBarrier和CountDownLatch

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

首先来一些简单的区别:

CountDownLatch CyclicBarrier
减计数方式 加计数方式
计算为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个线程互相等待任何一个线程完成之前,所有线程都必须等待。比如一群从不同地方相约去旅游,只有所有人都到某个景点的时候,这群人才能向下一个景点出发。








你可能感兴趣的:(并发编程,并发,java)