47 通过CountDownLatch解决公园游船问题
Diego38 24 1

1. 前言

本章开始我们将学习几种常见的Java并发工具,这些工具被用到很多控制并发的场景,是并发流程控制的一种手段。

本节要介绍的CountDownLatch在各种流行的中间件中出现频次比较高,面试中也经常出现,并且常常与同步屏障CycliBarrier进行对比,需要用心学习

2. CountDownLatch介绍

CountDownLatch中的Latch是门闩的意思,CountDown是倒计时的意思,CountDownLatch是当状态达到一定的数量条件后,才会打开门闩。

举一个场景,行李箱中有密码锁,一共有4个数字,每个数字都正确才能打开行李箱锁,意味着这个状态State=4,当state降为0时,锁就可以打开了。

再比如火箭发射前,需要经过4个步骤,这些步骤可以是并行执行,当4个步骤都完成就可以发射火箭了。

想象秒杀场景,时钟10秒倒计时,到点大家就可以抢购了,这里的抢购可以换成任意的比如赛跑,吃饭。

一个或多线程等待其他某些线程完成一些task之后再去执行,都可以使用CountDownLatch来完成。接下来我们借助一个典型的公园游园场景来演示CountDownLatch的用法。

3. 通过CountDownLatch解决公园游船问题

CountDownLatch有两个核心API方法

  • countDown 表示降低设置的初始状态,状态以整形表示,每次执行countDown都会将状态数量减1,直到零为止,执行CountDown的线程可以是一个或多个。

  • await/await(long time, TimeUnit unit) 阻塞当前线程,直到State状态降为0为止,执行await操作的线程可以有一个或多个。

看如下场景:公园中游船有四个座位,需要集齐4个人才能开船。

我们设置初始状态为4,每当有个上船时执行CountDown减1,当状态为0时,那么游船就可以启动了。

代码如下:

public class CountDownTest {

    private static CountDownLatch latch = new CountDownLatch(4);
    public static void main(String[] args) throws InterruptedException {
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 4; i++) {
            fixedThreadPool.submit(() -> {
                System.out.println("有人上船了");
                latch.countDown();
            });
        }
        System.out.println("还未达到4个人");
        latch.await();
        System.out.println("4人到齐了");

    }
}

输出如下:

有人上船了
有人上船了
有人上船了
还未达到4个人
有人上船了
4人到齐了

从上述代码看出,main线程在执行await时发现只有三人上船,状态停留在1,而不是0,此时会阻塞主线程,当其他线程执行最后一个线程的CountDown后,达到4人到齐的状态,才能开船。

这就是利用CountDownLatch完成生活场景的例子,我们看下CountDownLatch的实现。

我们之前重点讲过AQS,AQS是并发编程组件的引擎,并发工具类CountDownLatch也是借助AQS的能力,完成锁等待和状态变更的。

  • CountDown利用的是共享锁,state状态是countdown的个数
  • await发起获锁,countdown去释放锁,每次countdown会将state减去一个参数,当state为0,放锁成功。当state为0,await获锁成功,否则获取失败。state一旦为0不能重置
public class CountDownLatch {
    /**
     * Synchronization control For CountDownLatch.
     * Uses AQS state to represent count.
     */
    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) {
            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;
            }
        }
    }

countDownLatch中实现了一个Sync继承了AQS,当做CountDown时实际上做释放共享许可,当做await操作实际上是获取共享许可过程。Sync类中定义了tryAcquireShared和 tryReleaseShared,即规定了获取锁和释放锁的条件。

4. 总结

CountDownLatch用于满足一定条件后执行某些操作,但是只能使用一次,维护状态state变量是不能重新赋值的,底层是通过继承AQS来实现的。

预览图
评论区

索引目录