Java provides a rich set of concurrency utilities in the java.util.concurrent package to help manage thread synchronization and coordination. Among these utilities, CountDownLatch, CyclicBarrier, and Semaphore are commonly used for managing the coordination between threads, controlling access to shared resources, and improving overall concurrency performance. These utilities help make multithreaded programming easier and more efficient.
1. CountDownLatch
CountDownLatch is a synchronization aid that allows one or more threads to wait until a set of operations performed by other threads completes. It is initialized with a given count, and every time a thread completes an operation, it decrements the count. Threads waiting on the latch are released when the count reaches zero.
- Use Case: Useful when you need to wait for multiple threads to complete a specific task before proceeding with the next task.
- How it works: When the count reaches zero, all waiting threads are released.
Example:
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
int numberOfThreads = 3;
CountDownLatch latch = new CountDownLatch(numberOfThreads);
// Worker threads
for (int i = 0; i < numberOfThreads; i++) {
new Thread(new Worker(latch)).start();
}
latch.await(); // Main thread waits until the latch count reaches 0
System.out.println("All threads have completed their work.");
}
}
class Worker implements Runnable {
private final CountDownLatch latch;
Worker(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + " is working.");
Thread.sleep(1000); // Simulate work
latch.countDown(); // Decrease the count of the latch
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
Key points:
await()
: Makes the calling thread wait until the latch count is zero.countDown()
: Decreases the latch count, signaling a task’s completion.getCount()
: Returns the current count of the latch.
2. CyclicBarrier
CyclicBarrier is used to synchronize a group of threads, allowing them to wait for each other at a common barrier point before proceeding. Once all threads reach the barrier point, they are released simultaneously. It is called “cyclic” because it can be reused after all threads have passed through the barrier.
- Use Case: Useful when you need multiple threads to perform parts of a task in parallel and wait for each other at certain points in the process.
- How it works: Once the specified number of threads (parties) reach the barrier, they are released together.
Example:
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierExample {
public static void main(String[] args) throws InterruptedException {
int numberOfThreads = 3;
CyclicBarrier barrier = new CyclicBarrier(numberOfThreads, () -> System.out.println("All threads are ready to proceed."));
// Worker threads
for (int i = 0; i < numberOfThreads; i++) {
new Thread(new Worker(barrier)).start();
}
}
}
class Worker implements Runnable {
private final CyclicBarrier barrier;
Worker(CyclicBarrier barrier) {
this.barrier = barrier;
}
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + " is ready.");
barrier.await(); // Wait for all threads to reach the barrier
System.out.println(Thread.currentThread().getName() + " is proceeding.");
} catch (Exception e) {
Thread.currentThread().interrupt();
}
}
}
Key points:
await()
: Each thread calls this method to wait until all threads reach the barrier.reset()
: Resets the barrier for reuse after the initial completion.getNumberWaiting()
: Returns the number of threads currently waiting at the barrier.
3. Semaphore
A Semaphore is a counting synchronization primitive used to control access to a shared resource by multiple threads. It maintains a set of permits. A thread can acquire a permit before proceeding, and it can release the permit when done, allowing other threads to acquire it. If no permits are available, threads will block until a permit is released.
- Use Case: Useful for limiting the number of threads that can access a particular resource (e.g., database connections or thread pools).
- How it works: It can be initialized with a specific number of permits, and threads acquire or release these permits as needed.
Example:
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
public static void main(String[] args) throws InterruptedException {
int availablePermits = 2;
Semaphore semaphore = new Semaphore(availablePermits);
// Worker threads
for (int i = 0; i < 5; i++) {
new Thread(new Worker(semaphore)).start();
}
}
}
class Worker implements Runnable {
private final Semaphore semaphore;
Worker(Semaphore semaphore) {
this.semaphore = semaphore;
}
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + " is waiting for a permit.");
semaphore.acquire(); // Acquire a permit
System.out.println(Thread.currentThread().getName() + " acquired a permit.");
Thread.sleep(2000); // Simulate work
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
System.out.println(Thread.currentThread().getName() + " is releasing the permit.");
semaphore.release(); // Release the permit
}
}
}
Key points:
acquire()
: Acquires a permit from the semaphore, blocking if none are available.release()
: Releases a permit, allowing another thread to acquire it.availablePermits()
: Returns the number of available permits.tryAcquire()
: Attempts to acquire a permit, returningtrue
if successful andfalse
otherwise.
Use Cases and Comparison
- CountDownLatch is ideal for scenarios where a task needs to wait for multiple threads to finish their work (e.g., waiting for multiple database connections to be initialized).
- CyclicBarrier is useful when you need threads to wait for each other at specific points and then continue together, such as in parallel computations or simulations.
- Semaphore is ideal when you need to limit the number of threads accessing a shared resource at the same time, such as limiting database connections or thread pools.
Advantages of These Utilities
- CountDownLatch: Ensures that the main thread or a thread waits for multiple worker threads to complete before proceeding.
- CyclicBarrier: Useful for synchronizing threads in a repeated fashion, where they need to perform tasks in phases.
- Semaphore: Allows managing concurrency by restricting the number of threads that can access a resource simultaneously.