CountDownLatch vs. Semaphore – CountDownLatch 与 Semaphore 的比较

最后修改: 2024年 2月 6日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.导言

In Java multithreading, effective coordination between threads is crucial to ensure proper synchronization and prevent data corruption. Two commonly used mechanisms for thread coordination are CountDownLatch and Semaphore. In this tutorial, we’ll explore the differences between CountDownLatch and Semaphore and discuss when to use each.

在 Java 多线程中,线程之间的有效协调对于确保正确同步和防止数据损坏至关重要。两种常用的线程协调机制是 CountDownLatchSemaphore 。在本教程中,我们将探讨 CountDownLatchSemaphore 之间的区别,并讨论何时使用这两种方法。

2. Background

2. 背景

Let’s explore the fundamental concepts behind these synchronization mechanisms.

让我们来探讨一下这些同步机制背后的基本概念。

2.1. CountDownLatch

2.1.倒数锁定</em

CountDownLatch enables one or more threads to pause gracefully until a specified set of tasks has been completed. It operates by decrementing a counter until it reaches zero, indicating that all prerequisite tasks have finished.

CountDownLatch可让一个或多个线程优雅地暂停,直到一组指定的任务完成。其操作方法是递减计数器,直到计数器为零,表明所有前提任务均已完成。

2.2. Semaphore

2.2.Semaphore</em

Semaphore is a synchronization tool that controls access to a shared resource through the use of permits. In contrast to CountDownLatch, Semaphore permits can be released and acquired multiple times throughout the application, allowing finer-grained control over concurrency management.

Semaphore 是一种同步工具,可通过使用许可证来控制对共享资源的访问。CountDownLatch 不同的是,Semaphore 许可证可在整个应用程序中多次释放和获取,从而实现对并发管理更精细的控制。

3. Differences Between CountDownLatch and Semaphore

3.CountDownLatchSemaphore 之间的区别

In this section, we’ll delve into the key distinctions between these synchronization mechanisms.

在本节中,我们将深入探讨这些同步机制之间的主要区别。

3.1. Counting Mechanism

3.1.计算机制

CountDownLatch operates by starting with an initial count, which is decremented as tasks are completed. Once the count reaches zero, the waiting threads are released.

CountDownLatch以初始计数开始运行,随着任务的完成而递减。一旦计数为零,等待的线程就会被释放。

Semaphore maintains a set of permits, where each permit represents permission to access a shared resource. Threads acquire permits to access the resources and release them when finished.

Semaphore 维护一组许可证,其中每个许可证代表访问共享资源的权限。线程获取访问资源的许可,并在完成后释放这些许可。

3.2. Resettability

3.2. 可重置性

Semaphore permits can be released and acquired multiple times, allowing for dynamic resource management. For example, if our application suddenly requires more database connections, we can release additional permits to increase the number of available connections dynamically.

Semaphore 许可证可以多次释放和获取,从而实现动态资源管理。例如,如果我们的应用程序突然需要更多的数据库连接,我们可以释放额外的许可证,动态地增加可用连接的数量。

While in CountDownLatch, once the count reaches zero, it cannot be reset or reused for another synchronization event. It’s designed for one-time use cases.

而在 CountDownLatch 中,一旦计数为零,就不能重置或重新用于另一个同步事件。它专为一次性使用情况而设计。

3.3. Dynamic Permit Count

3.3.动态许可计数

Semaphore permits can be dynamically adjusted at runtime using the acquire() and release() methods. This allows for dynamic changes in the number of threads allowed to access a shared resource concurrently.

Semaphore 许可证可在运行时使用 acquire()release() 方法动态调整。这允许动态更改允许并发访问共享资源的线程数。

On the other hand, once CountDownLatch is initialized with a count, it remains fixed and cannot be altered during runtime.

另一方面,一旦 CountDownLatch 被初始化为一个计数,它就会保持固定,在运行时无法更改。

3.4. Fairness

3.4.公平

Semaphore supports the concept of fairness, which ensures that threads waiting to acquire permits are served in the order they arrived (first-in-first-out). This helps prevent thread starvation in high-contention scenarios.

Semaphore 支持公平性概念,可确保等待获取许可的线程按到达顺序(先进先出)获得服务。这有助于防止线程在高滞留场景中出现饥饿。

In contrast, CountDownLatch doesn’t have a fairness concept. It’s commonly used for one-time synchronization events where the specific order of thread execution is less critical.

相比之下,CountDownLatch 没有公平性概念。 它常用于一次性同步事件,在这种情况下,线程执行的具体顺序并不那么重要。

3.5. Use Cases

3.5.使用案例

CountDownLatch is commonly used for scenarios such as coordinating the startup of multiple threads, waiting for parallel operations to complete, or synchronizing the initialization of a system before proceeding with main tasks. For example, in a concurrent data processing application, CountDownLatch can ensure that all data loading tasks are completed before data analysis begins.

CountDownLatch 通常用于协调多个线程的启动、等待并行操作完成或在执行主要任务前同步系统初始化等场景。例如,在并发数据处理应用程序中,CountDownLatch 可确保在数据分析开始前完成所有数据加载任务。

On the other hand, Semaphore is suitable for managing access to shared resources, implementing resource pools, controlling access to critical sections of code, or limiting the number of concurrent database connections. For instance, in a database connection pooling system, Semaphore can limit the number of concurrent database connections to prevent overwhelming the database server.

另一方面,Semaphore 适用于管理对共享资源的访问、实施资源池、控制对关键代码段的访问或限制并发数据库连接的数量。例如,在数据库连接池系统中,Semaphore 可以限制并发数据库连接的数量,以防止数据库服务器不堪重负。

3.6. Performance

3.6.性能

Since CountDownLatch primarily involves decrementing a counter, it incurs minimal overhead in terms of processing and resource utilization. Moreover, Semaphore introduces overhead in managing permits, particularly when acquiring and releasing permits frequently. Each call to acquire() and release() involves additional processing to manage the permit count, which can impact performance, especially in scenarios with high concurrency.

由于 CountDownLatch 主要涉及计数器的递减,因此在处理和资源利用方面产生的开销极小。此外,Semaphore 在管理许可方面也会产生开销,尤其是在频繁获取和释放许可时。每次调用 acquire()release() 都会涉及管理许可计数的额外处理,这可能会影响性能,尤其是在高并发场景中。

3.7. Summary

3.7.总结

This table summarizes the key differences between CountDownLatch and Semaphore across various aspects:

本表总结了 CountDownLatchSemaphore 在各个方面的主要区别:

Feature CountDownLatch Semaphore
Purpose Synchronize threads until a set of tasks completes Control access to shared resources
Counting Mechanism Decrements a counter Manages permits (tokens)
Resettability Not resettable (one-time synchronization) Resettable (permits can be released and acquired multiple times)
Dynamic Permit Count No Yes (permits can be adjusted at runtime)
Fairness No specific fairness guarantee Provides fairness (first-in-first-out order)
Performance Low overhead (minimal processing) Slightly higher overhead due to permit management

4. Comparison in Implementation

4.实施情况比较

In this section, we’ll highlight the differences between how CountDownLatch and Semaphore are implemented in syntax and functionality.

在本节中,我们将重点介绍 CountDownLatchSemaphore 在语法和功能上的不同之处。

4.1. CountDownLatch Implementation

4.1.CountDownLatch 实现

First, we create a CountDownLatch with an initial count equal to the number of tasks to be completed. Each worker thread simulates a task and decrements the latch count using the countDown() method upon task completion. The main thread waits for all tasks to be completed using the await() method:

首先,我们创建一个 CountDownLatch,其初始计数等于要完成的任务数。每个工作线程模拟一个任务,并在任务完成后使用 countDown() 方法递减锁存器计数。主线程使用 await() 方法等待所有任务完成:

int numberOfTasks = 3;
CountDownLatch latch = new CountDownLatch(numberOfTasks);

for (int i = 1; i <= numberOfTasks; i++) {
    new Thread(() -> {
        System.out.println("Task completed by Thread " + Thread.currentThread().getId());
        latch.countDown();
    }).start();
}

latch.await();
System.out.println("All tasks completed. Main thread proceeds.");

After all tasks are completed and the latch count reaches zero, attempting to call countDown() will have no effect. Additionally, since the latch count is already zero, any subsequent call to await() returns immediately without blocking the thread:

在所有任务完成且锁存器计数为零后,尝试调用 countDown() 将不会有任何效果。此外,由于锁存器计数已经为零,任何对 await() 的后续调用都会立即返回,而不会阻塞线程:

latch.countDown();
latch.await(); // This line won't block
System.out.println("Latch is already at zero and cannot be reset.");

Let’s now observe the program’s execution and examine the output:

现在让我们观察程序的执行情况并检查输出结果:

Task completed by Thread 11
Task completed by Thread 12
Task completed by Thread 13
All tasks completed. Main thread proceeds.
Latch is already at zero and cannot be reset.

4.2. Semaphore Implementation

4.2.Semaphore的实现

In this example, we create a Semaphore with a fixed number of permits NUM_PERMITS. Each worker thread simulates resource access by acquiring a permit using the acquire() method before accessing the resource. One thing to take note is that, when a thread calls the acquire()  method to obtain a permit, it may be interrupted while waiting for the permit. Therefore, it’s essential to catch the InterruptedException within the trycatch block to handle this interruption gracefully.

在本示例中,我们创建了一个具有固定许可数 NUM_PERMITSSemaphore。每个工作线程在访问资源前都会使用 acquire() 方法获取一个许可证,从而模拟资源访问。需要注意的一点是,当线程调用 acquire() 方法获取许可证时,它可能会在等待许可证的过程中被中断。因此,必须在 trycatch 块中捕获 InterruptedException 以优雅地处理这种中断。

After completing resource access, the thread releases the permit using the release() method:

完成资源访问后,线程会使用 release() 方法释放许可证:

int NUM_PERMITS = 3;
Semaphore semaphore = new Semaphore(NUM_PERMITS);

for (int i = 1; i <= 5; i++) {
    new Thread(() -> {
        try {
            semaphore.acquire();
            System.out.println("Thread " + Thread.currentThread().getId() + " accessing resource.");
            Thread.sleep(2000); // Simulating resource usage
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            semaphore.release();
        }
    }).start();
}

Next, we simulate resetting the Semaphore by releasing additional permits to bring the count back to the initial permit value. This demonstrates that Semaphore permits can be dynamically adjusted or reset during runtime:

接下来,我们模拟重置 Semaphore 的过程,释放额外的许可,使计数恢复到初始许可值。这表明,Semaphore 许可证可以在运行期间动态调整或重置:

try {
    Thread.sleep(5000);
    semaphore.release(NUM_PERMITS); // Resetting the semaphore permits to the initial count
    System.out.println("Semaphore permits reset to initial count.");
} catch (InterruptedException e) {
    e.printStackTrace();
}

The following is the output after running the program:

运行程序后的输出结果如下:

Thread 11 accessing resource.
Thread 12 accessing resource.
Thread 13 accessing resource.
Thread 14 accessing resource.
Thread 15 accessing resource.
Semaphore permits reset to initial count.

5. Conclusion

5.结论

In this article, we’ve explored the key characteristics of both CountDownLatch and Semaphore. CountDownLatch is ideal for scenarios where a fixed set of tasks needs to be completed before allowing threads to proceed, making it suitable for one-time synchronization events. In contrast, Semaphore is used to control access to shared resources by limiting the number of threads that can access them concurrently, providing finer-grained control over concurrency management.

在本文中,我们探讨了 CountDownLatchSemaphore 的关键特性。CountDownLatch非常适合在允许线程继续执行之前需要完成一组固定任务的应用场景,因此适用于一次性同步事件。相比之下,Semaphore 通过限制可并发访问共享资源的线程数量来控制对共享资源的访问,从而对并发管理提供更精细的控制。

As always, the source code for the examples is available over on GitHub.

与往常一样,这些示例的源代码可在 GitHub 上获取。