什么是CountDownLatch? 缺乏、安全感 2021-09-30 23:14 324阅读 0赞 ![640?wx\_fmt=jpeg][640_wx_fmt_jpeg] > 本文公众号来源:crossoverJie > 作者:crossoverJie > > > > > 这是一篇关于CountDownLatch的文章,写得非常通俗易懂,在这给大家分享一下。 > > > > > 在之前我自己也写过(CountDownLatch、CyclicBarrier、Semaphore)的用法,如果对这些不了解的同学可以先去读读:[Java多线程打辅助的三个小伙子][Java] > > > # 前言 # 目录如下: ![640?wx\_fmt=jpeg][640_wx_fmt_jpeg 1] 在面试过程中聊到并发相关的内容时,不少面试官都喜欢问这类问题: > 当 N 个线程同时完成某项任务时,如何知道他们都已经执行完毕了。 CountDownLatch这就是这类面试题解决的关键。 # 自己实现 # 其实这类问题的核心论点都是:如何在一个线程中得知其他线程是否执行完毕。 假设现在有 3 个线程在运行,需要在主线程中得知他们的运行结果;可以分为以下几步: * 定义一个计数器为 3。 * 每个线程完成任务后计数减一。 * 一旦计数器减为 0 则通知等待的线程。 所以也很容易想到可以利用等待通知机制来实现 按照这个思路自定义了一个 `MultipleThreadCountDownKit` 工具,构造函数如下: ![640?wx\_fmt=png][640_wx_fmt_png] 考虑到并发的前提,这个计数器自然需要保证线程安全,所以采用了 `AtomicInteger`。 所以在初始化时需要根据线程数量来构建对象。 ## 计数器减一 ## 当其中一个业务线程完成后需要将这个计数器减一,直到减为0为止。 /** * 线程完成后计数 -1 */ public void countDown(){ if (counter.get() <= 0){ return; } int count = this.counter.decrementAndGet(); if (count < 0){ throw new RuntimeException("concurrent error") ; } if (count == 0){ synchronized (notify){ notify.notify(); } } } 利用 `counter.decrementAndGet()` 来保证多线程的原子性,当减为 0 时则利用等待通知机制来 `notify` 其他线程。 ## 等待所有线程完成 ## 而需要知道业务线程执行完毕的其他线程则需要在未完成之前一直处于等待状态,直到上文提到的在计数器变为 0 时得到通知。 /** * 等待所有的线程完成 * @throws InterruptedException */ public void await() throws InterruptedException { synchronized (notify){ while (counter.get() > 0){ notify.wait(); } if (notifyListen != null){ notifyListen.notifyListen(); } } } 原理也很简单,一旦计数器还存在时则会利用 `notify` 对象进行等待,直到被业务线程唤醒。 同时这里新增了一个通知接口可以自定义实现唤醒后的一些业务逻辑,后文会做演示。 ## 并发测试 ## 主要就是这两个函数,下面来做一个演示。 ![640?wx\_fmt=png][640_wx_fmt_png 1] * 初始化了三个计数器的并发工具 `MultipleThreadCountDownKit` * 创建了三个线程分别执行业务逻辑,完毕后执行 `countDown()`。 * 线程 3 休眠了 2s 用于模拟业务耗时。 * 主线程执行 `await()` 等待他们三个线程执行完毕。 ![640?wx\_fmt=png][640_wx_fmt_png 2] 通过执行结果可以看出主线程会等待最后一个线程完成后才会退出;从而达到了主线程等待其余线程的效果。 MultipleThreadCountDownKit multipleThreadKit = new MultipleThreadCountDownKit(3); multipleThreadKit.setNotify(() -> LOGGER.info("三个线程完成了任务")); 也可以在初始化的时候指定一个回调接口,用于接收业务线程执行完毕后的通知。 ![640?wx\_fmt=png][640_wx_fmt_png 3] 当然和在主线程中执行这段逻辑效果是一样的(和执行 `await()` 方法处于同一个线程)。 # CountDownLatch # 当然我们自己实现的代码没有经过大量生产环境的验证,所以主要的目的还是尝试窥探官方的实现原理。 所以我们现在来看看 `juc` 下的 `CountDownLatch` 是如何实现的。 ![640?wx\_fmt=jpeg][640_wx_fmt_jpeg 2] 通过构造函数会发现有一个 内部类 `Sync`,他是继承于 `AbstractQueuedSynchronizer` ;这是 Java 并发包中的基础框架,都可以单独拿来讲了,所以这次重点不是它,今后我们再着重介绍。 > 这里就可以把他简单理解为提供了和上文类似的一个计数器及线程通知工具就行了。 ## countDown ## 其实他的核心逻辑和我们自己实现的区别不大。 public void countDown() { sync.releaseShared(1); } public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { doReleaseShared(); return true; } return false; } 利用这个内部类的 `releaseShared` 方法,我们可以理解为他想要将计数器减一。 ![640?wx\_fmt=jpeg][640_wx_fmt_jpeg 3] 看到这里有没有似曾相识的感觉。 ![640?wx\_fmt=png][640_wx_fmt_png 4] 没错,在 `JDK1.7` 中的 `AtomicInteger` 自减就是这样实现的(利用 CAS 保证了线程安全)。 只是一旦计数器减为 0 时则会执行 `doReleaseShared` 唤醒其他的线程。 ![640?wx\_fmt=jpeg][640_wx_fmt_jpeg 4]![640?wx\_fmt=png][640_wx_fmt_png 5] 这里我们只需要关心红框部分(其他的暂时不用关心,这里涉及到了 AQS 中的队列相关),最终会调用 `LockSupport.unpark` 来唤醒线程;就相当于上文调用 `object.notify()`。 所以其实本质上还是相同的。 ## await ## 其中的 `await()` 也是借用 `Sync` 对象的方法实现的。 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); } protected int tryAcquireShared(int acquires) { return (getState() == 0) ? 1 : -1; } 一旦还存在未完成的线程时,则会调用 `doAcquireSharedInterruptibly` 进入阻塞状态。 ![640?wx\_fmt=jpeg][640_wx_fmt_jpeg 5] private final boolean parkAndCheckInterrupt() { LockSupport.park(this); return Thread.interrupted(); } 同样的由于这也是 `AQS` 中的方法,我们只需要关心红框部分;其实最终就是调用了 `LockSupport.park` 方法,也就相当于执行了 `object.wait()` 。 * 所有的业务线程执行完毕后会在计数器减为 0 时调用 `LockSupport.unpark` 来唤醒线程。 * 等待线程一旦计数器 > 0 时则会利用 `LockSupport.park` 来等待唤醒。 这样整个流程也就串起来了,它的使用方法也和上文的类似。 ![640?wx\_fmt=jpeg][640_wx_fmt_jpeg 6] 就不做过多介绍了。 # 总结 # `CountDownLatch` 算是 `juc` 中一个高频使用的工具,学会和理解他的使用会帮助我们更容易编写并发应用。 文中涉及到的源码: https://github.com/crossoverJie/JCSprout/blob/master/src/main/java/com/crossoverjie/concurrent/communication/MultipleThreadCountDownKit.java **你的点赞与分享是对我最大的支持** **![640?wx\_fmt=jpeg][640_wx_fmt_jpeg 7]** ![640?][640] **更多推荐内容** **↓↓↓** [通俗易懂讲解一条SQL是怎么执行的][SQL] [互联网公司时尚穿搭指南][Link 1] [什么是DDoS攻击?][DDoS] [如果还不懂Git和GitHub,瓜都吃不懂了!][Git_GitHub] [花了一天整理了一些我常用的工具][Link 2] [640_wx_fmt_jpeg]: /images/20210827/90c6ac34209141cc889f97e782e5a576.png?wx_fmt=jpeg [Java]: https://mp.weixin.qq.com/s?__biz=MzI4Njg5MDA5NA==&mid=2247484296&idx=1&sn=6bc82072500dda2798f567f1442f25ab&scene=21#wechat_redirect [640_wx_fmt_jpeg 1]: /images/20210827/2d2113f4898447a69de9a5590d04f4eb.png?wx_fmt=jpeg [640_wx_fmt_png]: /images/20210827/57b5a29556e3460a91ee1bc319b9d577.png?wx_fmt=png [640_wx_fmt_png 1]: /images/20210827/30301c20a7254c62b3c87376af4f7516.png?wx_fmt=png [640_wx_fmt_png 2]: /images/20210827/29c0bd81e3e7425995490a92478dc2c4.png?wx_fmt=png [640_wx_fmt_png 3]: /images/20210827/9a82a4025cc043949ad8b5dc4e4e5874.png?wx_fmt=png [640_wx_fmt_jpeg 2]: /images/20210827/39e9d0b1ea3748b4b48af8254fd6796c.png?wx_fmt=jpeg [640_wx_fmt_jpeg 3]: /images/20210827/fe2b4af247dc4d3d916a9df5356dc761.png?wx_fmt=jpeg [640_wx_fmt_png 4]: /images/20210827/1b49ddf82c224d9c9f5f04f1399f0611.png?wx_fmt=png [640_wx_fmt_jpeg 4]: https://ss.csdn.net/p?https://mmbiz.qpic.cn/mmbiz_jpg/csD7FygBVl1uqvpFgBMJanuWYDgr9AyhJPzmiaamGEPvQiaNzZVkkZ6XDMpIcFh64nlSLfVlbmPaLbtz3O9GEVVw/640?wx_fmt=jpeg [640_wx_fmt_png 5]: /images/20210827/024d2a861feb434e905f9ed2eed9e40a.png?wx_fmt=png [640_wx_fmt_jpeg 5]: /images/20210827/8848c08d5cf84d7a8d7d5cecd5d2c9ad.png?wx_fmt=jpeg [640_wx_fmt_jpeg 6]: /images/20210827/e5b3fdae252b4aaab0749c815fa768f2.png?wx_fmt=jpeg [640_wx_fmt_jpeg 7]: /images/20210827/febc55bff1054ea4aefd4872471ad682.png?wx_fmt=jpeg [640]: /images/20210827/e6b9a742066d4d8cb83eaeb42168629f.png? [SQL]: https://mp.weixin.qq.com/s?__biz=MzI4Njg5MDA5NA==&mid=2247485172&idx=1&sn=30cec1b746a4156c39a0ac3535e5205c&chksm=ebd747f5dca0cee3ae618dec83d49c3e7877fca68f8b83bd633a3f9349d4627ff8d495a42f7c&token=1668388089&lang=zh_CN&scene=21#wechat_redirect [Link 1]: https://blog.csdn.net/csdnnews/article/details/83247399 [DDoS]: https://mp.weixin.qq.com/s?__biz=MzI4Njg5MDA5NA==&mid=2247485159&idx=1&sn=e4e4b37fc9cd2684340e026e59909f74&chksm=ebd747e6dca0cef0fb6760297ba2f42d7221485adae205a0dffc473fb40b3c7d63a66543e32f&token=1668388089&lang=zh_CN&scene=21#wechat_redirect [Git_GitHub]: https://mp.weixin.qq.com/s?__biz=MzI4Njg5MDA5NA==&mid=2247485143&idx=1&sn=18658c3564aed6b814529a3ef26e55cf&chksm=ebd747d6dca0cec048eb8ac8f57da9ef6eb7c1e17276328b17d0e445b2a82fe4b0ed42a7b1f5&token=1668388089&lang=zh_CN&scene=21#wechat_redirect [Link 2]: https://mp.weixin.qq.com/s?__biz=MzI4Njg5MDA5NA==&mid=2247485140&idx=1&sn=783d7a9bb272a81dfa5f182d4da231fc&chksm=ebd747d5dca0cec38b7d746b928bf762617ea114cbcd4559830f62c62572e7c84b9009fb226a&token=1668388089&lang=zh_CN&scene=21#wechat_redirect
还没有评论,来说两句吧...