Java中CountDownLatch与Semaphore比较

💡 原文中文,约3700字,阅读约需9分钟。
📝

内容提要

在Java多线程中,CountDownLatch和Semaphore是常用的线程协调机制。CountDownLatch通过递减计数器来暂停线程,直到完成一组任务。Semaphore通过许可来控制对共享资源的访问。CountDownLatch适用于一次性同步事件,而Semaphore适用于动态资源管理。CountDownLatch的计数是不可重置的,而Semaphore的许可可以多次释放和获取。CountDownLatch的性能较好,而Semaphore在频繁获取和释放许可时性能较差。

🔎

延伸解读

核心差异:一次性同步 vs 动态资源管理

CountDownLatch 的计数在初始化后固定,且一旦归零便无法重置,因此只适用于一次性同步事件,例如等待所有数据加载任务完成后再启动分析。Semaphore 的许可数量可在运行时通过 acquire() 和 release() 动态调整,适合需要反复获取和释放资源的场景,如限制并发数据库连接数。选择时需明确同步需求是一次性的还是持续动态的。

公平性:Semaphore 支持 FIFO,CountDownLatch 不保证

Semaphore 支持公平模式,等待许可的线程按到达顺序(先进先出)获得服务,这有助于在高争用场景中防止线程饥饿。而 CountDownLatch 没有公平性概念,它通常用于一次性同步事件,线程执行的特定顺序并不重要。因此,若对线程获取资源的顺序有严格要求,应优先考虑 Semaphore。

性能考量:CountDownLatch 开销更小

CountDownLatch 主要涉及递减计数器,处理和资源利用开销最小。Semaphore 在管理许可时会产生额外开销,尤其是在频繁调用 acquire() 和 release() 的高并发场景下,可能影响性能。因此,在性能敏感且只需一次性同步的场景中,CountDownLatch 是更轻量的选择。

使用注意:中断处理与不可重置性

使用 Semaphore 时,acquire() 方法可能抛出 InterruptedException,必须在 try-catch 块中捕获以优雅处理中断,并在 finally 中释放许可,避免许可泄漏。而 CountDownLatch 一旦计数归零,后续 countDown() 调用无效,await() 会立即返回,且无法重置。理解这些行为有助于避免误用。

❓

Q&A

CountDownLatch和Semaphore在计数机制上有什么不同?

CountDownLatch通过递减计数器工作,初始计数固定,任务完成时调用countDown()减少计数,计数到零后等待线程被释放。Semaphore维护一组许可,每个许可代表访问共享资源的权限,线程通过acquire()获取许可,release()释放许可。

CountDownLatch的计数可以重置吗?Semaphore的许可可以动态调整吗?

CountDownLatch的计数一旦达到零就无法重置或重新用于另一个同步事件,专为一次性用例设计。Semaphore的许可可以多次释放和获取,并且可以在运行时通过acquire()和release()方法动态调整许可数量。

CountDownLatch和Semaphore分别适用于哪些场景?

CountDownLatch适用于协调多个线程的启动、等待并行操作完成或在继续主任务前同步系统初始化等一次性同步事件。Semaphore适用于管理对共享资源的访问、实现资源池、控制关键代码访问或限制并发数据库连接数量等动态资源管理场景。

CountDownLatch和Semaphore在性能上有什么区别?

CountDownLatch主要涉及递减计数器,开销最小,性能较好。Semaphore在管理许可时带来开销,特别是在频繁获取和释放许可时,每次acquire()和release()调用都涉及额外处理,可能影响性能,尤其在高并发情况下。

Semaphore支持公平性吗?CountDownLatch呢?

Semaphore支持公平性,可以确保等待获取许可的线程按照到达顺序(先进先出)获得服务,有助于防止高争用场景中的线程饥饿。CountDownLatch没有公平概念,通常用于一次性同步事件,线程执行顺序不太重要。

如何使用CountDownLatch实现线程等待?

首先创建CountDownLatch并指定初始计数(等于要完成的任务数)。每个工作线程完成任务后调用countDown()减少计数。主线程调用await()方法等待,直到计数减到零,然后继续执行。计数到零后,后续countDown()无效,await()会立即返回。

如何使用Semaphore控制并发资源访问?

创建Semaphore并指定许可数量。线程在访问资源前调用acquire()获取许可,可能被中断,需在try-catch中处理InterruptedException。访问完成后在finally块中调用release()释放许可。还可以在运行时通过release()增加许可来动态调整并发数。

🏷️

标签

➡️

继续阅读