蹲厕所的熊

benjaminwhx

【细谈Java并发】谈谈CountDownLatch

2018-05-03 作者: 吴海旭


  1. 1、简介
  2. 2、使用场景
  3. 3、原理分析

1、简介

CountDownLatch也叫闭锁,它是J.U.C包中基于AQS实现的一个很简单的类,它允许一个或多个线程等待其他线程完成操作后再执行。

建议阅读CountDownLatch源码前,先深入研究一下AQS的原理,搞清楚什么是独占锁,什么是共享锁。这部分可以看我之前的文章:【细谈Java并发】谈谈AQS

CountDownLatch内部会维护一个资源数量为初始化值为的计数器,当A线程调用await方法后,A线程会在计数器大于0的时候一直阻塞等待。当一个线程完成任务后,计数器的值会减1。当计数器变为0时,表示所有的线程已经完成任务,等待的主线程被唤醒继续执行。

2、使用场景

在一些应用场合中,需要等待某个条件达到要求后才能做后面的事情。比如:主线程需要等待所有子线程处理完任务后需要拿到返回值继续执行,这时候就用到了CountDownLatch。

public class CountDownLatchTest {

    private final static CountDownLatch countDownLatch = new CountDownLatch(5);
    private final static ExecutorService executorService = Executors.newFixedThreadPool(5);

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 5; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        // 模拟执行任务
                        Thread.sleep(1000);
                        System.out.println(Thread.currentThread().getName() + "执行完任务");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        countDownLatch.countDown();
                    }
                }
            });
        }
        countDownLatch.await();
        System.out.println("主线程等待子线程执行任务完毕,继续执行");
    }
}

输出结果:

pool-1-thread-1执行完任务
pool-1-thread-5执行完任务
pool-1-thread-2执行完任务
pool-1-thread-4执行完任务
pool-1-thread-3执行完任务
主线程等待子线程执行任务完毕,继续执行

3、原理分析

上面的例子里,我们首先构造的时候传递了5个资源数量,并在主线程进行await,而每个子线程执行完了调用countDown方法,我们来看看这三个方法。

public CountDownLatch(int count) {
    if (count < 0) throw new IllegalArgumentException("count < 0");
    this.sync = new Sync(count);
}

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

public void countDown() {
    sync.releaseShared(1);
}

这几个方法啥都没做,所有的处理都在Sync这个类里,我们来看看这个AQS的子类吧。

private static final class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = 4982264981922014374L;

    Sync(int count) {
        setState(count);
    }

    int getCount() {
        return getState();
    }

    protected int tryAcquireShared(int acquires) {
        // 只有资源变为0才会获取到锁,否则进入队列阻塞等待
        return (getState() == 0) ? 1 : -1;
    }

    protected boolean tryReleaseShared(int releases) {
        // Decrement count; signal when transition to zero
        for (;;) {
            int c = getState();
            if (c == 0)
                return false;
            int nextc = c-1;
            if (compareAndSetState(c, nextc))
                return nextc == 0;
        }
    }
}

因为构造方法里面我们设置了资源值,所以在await的时候会调用tryAcquireShared返回-1进行阻塞等待。

而countDown方法则每次调用tryReleaseShared(1)进行资源-1的操作,当资源变为0时,唤醒Sync队列里的节点进行资源获取的操作,从而让阻塞的主线程又活跃起来。



坚持原创技术分享,您的支持将鼓励我继续创作!



分享

评论