美文网首页Java 杂谈程序员
【细谈Java并发】谈谈CountDownLatch

【细谈Java并发】谈谈CountDownLatch

作者: 蹲厕所的熊 | 来源:发表于2018-05-04 22:01 被阅读4次

    1、简介

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

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

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

    image

    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队列里的节点进行资源获取的操作,从而让阻塞的主线程又活跃起来。

    相关文章

      网友评论

      本文标题:【细谈Java并发】谈谈CountDownLatch

      本文链接:https://www.haomeiwen.com/subject/ckmjrftx.html