CountDownLatch
是 Java 提供的一个用于实现互斥锁的一种方式。它是一个轻量级的互斥机制,常用于处理需要同步多线程的任务(例如轮询、超时等待等)。与传统的 synchronized
关键字相比,CountDownLatch
更适合需要处理大量轮询请求或高并发场景的任务。
基本概念
- CountDownLatch 是一种互斥锁,它基于计数机制来实现同步。它的核心思想是:只有当计数器达到一个特定的值时(通常是0),线程才能进入临界区执行任务。
- 当一个子线程调用
countdownTo()
方法时,主线程会减少计数器的值,直到计数器归零为止。主线程可以重新进入临界区。 - 一旦临界区中的主线程唤醒后(通过
awake()
方法),它会将计数器增加到其最大值。
主要特点
-
轮询机制:
- 子线程可以通过调用
countdownTo(N)
方法将计数器减少到指定的值。 - 主线程调用
awake()
方法后,可以唤醒被轮询的子线程并重新获取锁。
- 子线程可以通过调用
-
高效性:
- 由于不使用锁的原子操作(如
lock
和unlock
),CountDownLatch
的性能优于传统的互斥锁。 - 它特别适合处理大量轮询请求,因为每个子线程只需要减少计数器一次即可唤醒主线程。
- 由于不使用锁的原子操作(如
-
轻量级:
CountDownLatch
是一种轻量级的互斥机制,因为它不需要维护锁结构或其他复杂的数据结构。
示例代码
以下是一个使用 CountDownLatch
实现轮询任务的例子:
public class CountDownLatchDemo {public static void main(String[] args) {// 创建一个计数器,并初始化为5CountDownLatch countDownLatch = new CountDownLatch(5);// 启动5个子线程for (int i = 0; i < 5; i++) {Thread thread = new Thread((Runnable) () -> {System.out.println("子线程" + i + "正在等待...");countDownLatch.countdownTo(1); // 将计数器减少到1,表示被轮询}).start();}// 启动主线程Runnable mainTask = () -> {System.out.println("\n主线程开始执行!");try {countDownLatch.awake(); // 唤醒被轮询的子线程(计数器归零)// 完成任务后重置计数器countDownLatch.countDownLatch(10); // 将计数器设置回5} catch (InterruptedException e) {Thread.currentThread().interrupt();}};new Runnable() {public void run() {mainTask(); // 启动主线程}}.start();System.out.println("\n主线程已执行完毕!");}
}
使用场景
- 轮询任务:当需要处理大量轮询请求时,
CountDownLatch
是一个高效的选择。例如,Web服务器处理并发请求时,可以使用CountDownLatch
来轮询不同的客户端。 - 超时等待:当某个操作失败后(如网络连接不通),可以使用
CountDownLatch
实现超时后的重试逻辑。
优缺点
优点:
- 高性能:相比传统的互斥锁,
CountDownLatch
的性能更好,尤其是在处理大量轮询请求时。 - 轻量级:不涉及锁的原子操作或其他复杂实现,适合频繁启动和停止线程的任务。
缺点:
- 无法直接获取锁:使用
CountDownLatch
时,需要手动管理计数器和唤醒逻辑。因此,在需要获取锁的情况下(如其他高并发操作),可能需要结合其他工具。 - 不支持批量轮询:如果需要同时处理多个轮询请求,可能需要手动实现。
总结
CountDownLatch
是一种高效、轻量级的互斥机制,特别适合用于轮询任务或超时等待场景。它通过计数器实现同步,避免了传统互斥锁的一些性能问题。然而,在某些情况下(例如需要直接获取锁时),可能还需要结合其他工具来使用。