CountDownLatch 用法详解
- CountDownLatch 详解
- 1 原理
- 2 常见用法
- 3 方法介绍
- 4 示例及使用
CountDownLatch 详解
CountDownLatch
(倒计时门闩)是Java并发包中的一个工具类,用于协调多个线程之间的同步。它允许一个或多个线程等待其他线程完成操作后再继续执行。
CountDownLatch 通过一个计数器来实现,该计数器在初始化时设定,然后递减。当计数器值为0时,等待的线程会被唤醒。
1 原理
CountDownLatch
是通过一个计数器来实现等待操作的,它的基本原理如下:
计数器的初始化值为线程的数量。每当一个线程完成了自己的任务后,计数器的值就相应得减1。当计数器到达0时,表示所有的线程都已完成任务,然后在闭锁上等待的线程就可以恢复执行任务。
2 常见用法
用法一
某一线程在开始运行前等待n个线程执行完毕。
- 创建 CountDownLatch 对象时,需要传入一个初始计数值。这个值表示需要等待的线程数量。
- 当一个线程完成了它的任务后,可以调用 countDown() 方法来递减计数器的值。
- 其他线程可以调用 await() 方法来阻塞等待,直到计数器值为0。
用法二
实现多个线程开始执行任务的最大并行性。注意是并行性,不是并发,强调的是多个线程在某一时刻同时开始执行。类似于赛跑,将多个线程放到起点,等待发令枪响,然后同时开跑。
- 初始化一个共享的CountDownLatch(1),将其计数器初始化为1。
- 多个线程在开始执行任务前首先 coundownlatch.await()。
- 当主线程调用 countDown() 时,计数器变为0,多个线程同时被唤醒。
结构:
CountDownLatch的UML类图如下:
CountDownLatch 的数据结构很简单,它是通过"共享锁"实现的。它包含了sync对象,sync是Sync类型。Sync是实例类,它继承于 AQS 。
源代码:
package java.util.concurrent;import java.util.concurrent.locks.AbstractQueuedSynchronizer;public class CountDownLatch {private final Sync sync;public CountDownLatch(int count) {if (count < 0) {throw new IllegalArgumentException("count < 0");} else {this.sync = new Sync(count);}}public void await() throws InterruptedException {this.sync.acquireSharedInterruptibly(1);}public boolean await(long timeout, TimeUnit unit) throws InterruptedException {return this.sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));}public void countDown() {this.sync.releaseShared(1);}public long getCount() {return (long)this.sync.getCount();}public String toString() {return super.toString() + "[Count = " + this.sync.getCount() + "]";}private static final class Sync extends AbstractQueuedSynchronizer {private static final long serialVersionUID = 4982264981922014374L;Sync(int count) {this.setState(count);}int getCount() {return this.getState();}protected int tryAcquireShared(int acquires) {return this.getState() == 0 ? 1 : -1;}protected boolean tryReleaseShared(int releases) {int c;int nextc;do {c = this.getState();if (c == 0) {return false;}nextc = c - 1;} while(!this.compareAndSetState(c, nextc));return nextc == 0;}}
}
从源码中可以看到:
内部类 Sync
继承了 AbstractQueuedSynchronizer
,实现了计数器的同步控制逻辑。
其中,tryAcquireShared(int acquires)
方法用于判断是否可以获取同步状态,当计数器为0时返回1,表示可以获取同步状态;
tryReleaseShared(int releases)
方法用于释放同步状态,将计数器减1,并使用compareAndSetState(c, nextc)
方法保证操作的原子性。
3 方法介绍
CountDownLatch 类主要提供了以下方法:
-
public CountDownLatch(int count)
- 构造函数,创建一个 CountDownLatch 对象,并设置初始的计数器值为 count 。
-
public void await() throws InterruptedException
- 阻塞当前线程,直到计数器值变为0。如果计数器已经为0,立即返回。如果在等待过程中被中断,则抛出 InterruptedException 异常。
-
public boolean await(long timeout, TimeUnit unit) throws InterruptedException
- 阻塞当前线程,直到计数器值变为0或者超时。如果在指定时间内计数器变为0,则返回 true ,否则返回 false 。如果在等待过程中被中断,则抛出 InterruptedException 异常。
-
public void countDown()
- 递减计数器的值,表示一个任务已经完成。
-
public long getCount()
- 获取当前计数器的值。
4 示例及使用
下面是一个简单的示例,演示了如何使用CountDownLatch
来实现线程之间的同步:
import java.util.concurrent.CountDownLatch;public class Main {public static void main(String[] args) throws InterruptedException {final int threadCount = 3;CountDownLatch latch = new CountDownLatch(threadCount);// 创建并启动线程for (int i = 0; i < threadCount; i++) {Thread thread = new Thread(() -> {// 模拟线程执行任务try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Thread " + Thread.currentThread().getId() + " finished its task.");// 任务完成后递减计数器latch.countDown();});thread.start();}// 主线程等待所有子线程完成任务latch.await();System.out.println("All threads have finished their tasks.");}
}
在上面的示例中,主线程通过latch.await()
等待所有子线程完成任务,而每个子线程完成任务后调用latch.countDown()
来递减计数器。当计数器值变为0时,主线程恢复执行。
在实时系统中的使用场景
- 实现最大的并行性:有时我们想同时启动多个线程,实现最大程度的并行性。例如,我们想测试一个单例类。如果我们创建一个初始计数器为1的CountDownLatch,并让其他所有线程都在这个锁上等待,只需要调用一次countDown()方法就可以让其他所有等待的线程同时恢复执行。
- 开始执行前等待N个线程完成各自任务:例如应用程序启动类要确保在处理用户请求前,所有N个外部系统都已经启动和运行了。
- 死锁检测:一个非常方便的使用场景是你用N个线程去访问共享资源,在每个测试阶段线程数量不同,并尝试产生死锁。