暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

【细谈Java并发】谈谈CountDownLatch

蹲厕所的熊 2018-05-06
489

蹲厕所的熊 转载请注明原创出处,谢谢!

1、简介

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

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

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

2、使用场景

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

  1. public class CountDownLatchTest {


  2.    private final static CountDownLatch countDownLatch = new CountDownLatch(5);

  3.    private final static ExecutorService executorService = Executors.newFixedThreadPool(5);


  4.    public static void main(String[] args) throws InterruptedException {

  5.        for (int i = 0; i < 5; i++) {

  6.            executorService.execute(new Runnable() {

  7.                @Override

  8.                public void run() {

  9.                    try {

  10.                        // 模拟执行任务

  11.                        Thread.sleep(1000);

  12.                        System.out.println(Thread.currentThread().getName() + "执行完任务");

  13.                    } catch (InterruptedException e) {

  14.                        e.printStackTrace();

  15.                    } finally {

  16.                        countDownLatch.countDown();

  17.                    }

  18.                }

  19.            });

  20.        }

  21.        countDownLatch.await();

  22.        System.out.println("主线程等待子线程执行任务完毕,继续执行");

  23.    }

  24. }

输出结果:

  1. pool-1-thread-1执行完任务

  2. pool-1-thread-5执行完任务

  3. pool-1-thread-2执行完任务

  4. pool-1-thread-4执行完任务

  5. pool-1-thread-3执行完任务

  6. 主线程等待子线程执行任务完毕,继续执行

3、原理分析

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

  1. public CountDownLatch(int count) {

  2.    if (count < 0) throw new IllegalArgumentException("count < 0");

  3.    this.sync = new Sync(count);

  4. }


  5. public void await() throws InterruptedException {

  6.    sync.acquireSharedInterruptibly(1);

  7. }


  8. public void countDown() {

  9.    sync.releaseShared(1);

  10. }

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

  1. private static final class Sync extends AbstractQueuedSynchronizer {

  2.    private static final long serialVersionUID = 4982264981922014374L;


  3.    Sync(int count) {

  4.        setState(count);

  5.    }


  6.    int getCount() {

  7.        return getState();

  8.    }


  9.    protected int tryAcquireShared(int acquires) {

  10.        // 只有资源变为0才会获取到锁,否则进入队列阻塞等待

  11.        return (getState() == 0) ? 1 : -1;

  12.    }


  13.    protected boolean tryReleaseShared(int releases) {

  14.        // Decrement count; signal when transition to zero

  15.        for (;;) {

  16.            int c = getState();

  17.            if (c == 0)

  18.                return false;

  19.            int nextc = c-1;

  20.            if (compareAndSetState(c, nextc))

  21.                return nextc == 0;

  22.        }

  23.    }

  24. }

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

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


如果读完觉得有收获的话,欢迎点赞、关注、加公众号【蹲厕所的熊】,查阅更多精彩历史!!!

文章转载自蹲厕所的熊,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论