java并发:线程协作机制之CountDownLatch
一、初识CountDownLatch
(1)概述
(2)应用场景
Application’s main thread wants to wait, till other service threads which are responsible for starting framework services have completed started all services.
二、详述CountDownLatch
CountDownLatch是通过一个计数器来实现的。
每当一个线程完成了自己的任务后,计数器的值就会减1,当计数器值到达0时,它表示所有的线程已经完成了任务,然后处于等待的线程就可以恢复执行任务。
下图展示了CountDownLatch中的方法:
- public CountDownLatch(int count)
构造函数,count(计数器)实际上就是需要等待的线程数量,这个值只能被设置一次(即:CountDownLatch没有提供任何机制去重新设置这个值)。
- public void countDown()
每调用一次这个方法,在构造函数中初始化的count值就减1。
- public void await() throws InterruptedException
调用此方法的线程会一直阻塞,直到计时器的值为0。
下图展示了CountDownLatch中内部类Sync的类图:
解释:
根据类图即可知道Sync继承了AbstractQueuedSynchronizer,进而可知CountDownLatch是使用 AQS 实现的。
构造函数
CountDownLatch的构造函数的定义如下:
/** * Constructs a {@code CountDownLatch} initialized with the given count. * * @param count the number of times {@link #countDown} must be invoked * before threads can pass through {@link #await} * @throws IllegalArgumentException if {@code count} is negative */ public CountDownLatch(int count) { if (count < 0) throw new IllegalArgumentException("count < 0"); this.sync = new Sync(count); }
解读:
构造函数创建了内部类Sync的实例。
Sync的定义如下:
/** * 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; } } }
解读:
通过上述代码可以发现,计数器count实际上是赋值给了 AQS 的状态变量 state。
await方法
其定义如下:
/** * Causes the current thread to wait until the latch has counted down to * zero, unless the thread is {@linkplain Thread#interrupt interrupted}. * * <p>If the current count is zero then this method returns immediately. * * <p>If the current count is greater than zero then the current * thread becomes disabled for thread scheduling purposes and lies * dormant until one of two things happen: * <ul> * <li>The count reaches zero due to invocations of the * {@link #countDown} method; or * <li>Some other thread {@linkplain Thread#interrupt interrupts} * the current thread. * </ul> * * <p>If the current thread: * <ul> * <li>has its interrupted status set on entry to this method; or * <li>is {@linkplain Thread#interrupt interrupted} while waiting, * </ul> * then {@link InterruptedException} is thrown and the current thread's * interrupted status is cleared. * * @throws InterruptedException if the current thread is interrupted * while waiting */ public void await() throws InterruptedException { sync.acquireSharedInterruptibly(1); }
解读:
上述方法实际上是调用了父类AbstractQueuedSynchronizer中的acquireSharedInterruptibly方法,其定义如下:
/** * Acquires in shared mode, aborting if interrupted. Implemented * by first checking interrupt status, then invoking at least once * {@link #tryAcquireShared}, returning on success. Otherwise the * thread is queued, possibly repeatedly blocking and unblocking, * invoking {@link #tryAcquireShared} until success or the thread * is interrupted. * @param arg the acquire argument. * This value is conveyed to {@link #tryAcquireShared} but is * otherwise uninterpreted and can represent anything * you like. * @throws InterruptedException if the current thread is interrupted */ public final void acquireSharedInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); if (tryAcquireShared(arg) < 0) doAcquireSharedInterruptibly(arg); }
解读:
根据AQS的实现原理,Sync需要实现tryAcquireShared方法。
请参见本文前面Sync的定义,Sync 中的 tryAcquireShared 方法查看当前状态值(计数器值)是否为 0,是则直接返回,否则调用 AQS 的 doAcquireSharedlnterruptibly 方法让当前线程阻塞。
countDown方法
/** * Decrements the count of the latch, releasing all waiting threads if * the count reaches zero. * * <p>If the current count is greater than zero then it is decremented. * If the new count is zero then all waiting threads are re-enabled for * thread scheduling purposes. * * <p>If the current count equals zero then nothing happens. */ public void countDown() { sync.releaseShared(1); }
解读:
上述方法实际上是调用了父类AbstractQueuedSynchronizer中的releaseShared方法,其定义如下:
/** * Releases in shared mode. Implemented by unblocking one or more * threads if {@link #tryReleaseShared} returns true. * * @param arg the release argument. This value is conveyed to * {@link #tryReleaseShared} but is otherwise uninterpreted * and can represent anything you like. * @return the value returned from {@link #tryReleaseShared} */ public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { doReleaseShared(); return true; } return false; }
解读:
根据AQS的实现原理,Sync需要实现tryReleaseShared方法。
请参见本文前面Sync的定义,Sync 中的 tryReleaseShared 方法首先获取当前状态值(计数器值) ,如果当前状态值为 0 则直接返回 false;否则使用 CAS 将计数器值减 1(在此过程中,CAS 如果失败则循环重试),如果计数器值被减为 0 则返回 true 以调用 AQS 的 doReleaseShared 方法来激活阻塞的线程。
三、示例
package com.test; import java.util.concurrent.CountDownLatch; public class CountDownLatchDemo{ public static void main(String args[]) throws Exception{ CountDownLatch latch = new CountDownLatch(3); Worker worker1 = new Worker("Jack 程序员1",latch); Worker worker2 = new Worker("Rose 程序员2",latch); Worker worker3 = new Worker("Json 程序员3",latch); worker1.start(); worker2.start(); worker3.start(); latch.await(); System.out.println("Main thread end!"); } static class Worker extends Thread { private String workerName; private CountDownLatch latch; public Worker(String workerName,CountDownLatch latch) { this.workerName = workerName; this.latch = latch; } @Override public void run() { try { System.out.println("Worker:"+workerName +" is begin."); Thread.sleep(1000L); System.out.println("Worker:"+workerName +" is end."); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); }//模仿干活; latch.countDown(); } } }
上述程序运行结果如下:
Worker:Rose 程序员2 is begin.
Worker:Json 程序员3 is begin.
Worker:Jack 程序员1 is begin.
Worker:Jack 程序员1 is end.
Worker:Json 程序员3 is end.
Worker:Rose 程序员2 is end.
Main thread end!
从结果上可以看出,MainThread执行到latch.await();处会阻塞在该处,直到三个线程均完成的时候MainThread才会继续往下执行
Note:
Other N threads must have a reference of the latch object because they will need to notify the CountDownLatch object that they have completed their task.
四、分析总结
问题:CountDownLatch与join方法的区别
在线程中调用某个子线程的join() 方法后,该线程将一直被阻塞直到子线程运行完毕,而 CountDownLatch通过使用计数器来使得子线程在运行完或者运行中递减计数,所以 CountDownLatch可以在子线程运行的任何时候让await方法返回,并不需要等到线程结束。
另外,使用线程池来管理线程时一般都是直接添加 Runable 到线程池,这时没有办法调用线程的 join 方法,所以 countDownLatch 相比 join 方法更灵活。
(1)https://howtodoinjava.com/java/multi-threading/when-to-use-countdownlatch-java-concurrency-example-tutorial/
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?