A semaphore is a synchronization primitive that controls access to a shared resource. It maintains a set of permits. When a thread wants to access the resource, it must acquire a permit from the semaphore. If a permit is available, the thread can proceed and the number of available permits is decreased. When the thread is done with the resource, it releases the permit, increasing the number of available permits.
There are two types of semaphores: binary semaphores and counting semaphores. Binary semaphores have only one permit, and they are often used to implement mutual exclusion (similar to synchronized
blocks). Counting semaphores can have multiple permits and are used to control access to a resource with a limited number of instances.
A barrier is a synchronization aid that allows a set of threads to wait for each other to reach a common point in their execution. When a thread reaches the barrier, it waits until all other threads in the set also reach the barrier. Once all threads have reached the barrier, they can all proceed.
Java provides two types of barriers: CyclicBarrier
and CountDownLatch
. A CyclicBarrier
can be reused after all threads have reached the barrier. A CountDownLatch
is a one - time use barrier; once the count reaches zero, the waiting threads are released and the latch cannot be reused.
In Java, semaphores are implemented using the java.util.concurrent.Semaphore
class. Here is a simple example of using a semaphore to control access to a limited number of resources:
import java.util.concurrent.Semaphore;
class Resource {
private static final int MAX_USERS = 3;
private Semaphore semaphore = new Semaphore(MAX_USERS);
public void useResource() {
try {
// Acquire a permit
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + " is using the resource.");
// Simulate some work
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// Release the permit
semaphore.release();
System.out.println(Thread.currentThread().getName() + " has released the resource.");
}
}
}
public class SemaphoreExample {
public static void main(String[] args) {
Resource resource = new Resource();
for (int i = 0; i < 5; i++) {
new Thread(() -> resource.useResource()).start();
}
}
}
In this example, we create a semaphore with 3 permits. Only 3 threads can access the resource at the same time. When a thread finishes using the resource, it releases the permit, allowing another thread to acquire it.
The CyclicBarrier
class in Java is used to create a reusable barrier. Here is an example:
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
class Task implements Runnable {
private CyclicBarrier barrier;
public Task(CyclicBarrier barrier) {
this.barrier = barrier;
}
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + " is waiting at the barrier.");
barrier.await();
System.out.println(Thread.currentThread().getName() + " has passed the barrier.");
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}
}
public class CyclicBarrierExample {
public static void main(String[] args) {
int numberOfThreads = 3;
CyclicBarrier barrier = new CyclicBarrier(numberOfThreads);
for (int i = 0; i < numberOfThreads; i++) {
new Thread(new Task(barrier)).start();
}
}
}
In this example, we create a CyclicBarrier
with 3 threads. Each thread waits at the barrier until all 3 threads have reached it. Then, all threads can proceed.
The CountDownLatch
class is used to create a one - time use barrier. Here is an example:
import java.util.concurrent.CountDownLatch;
class Worker implements Runnable {
private CountDownLatch latch;
public Worker(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + " is working.");
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " has finished working.");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// Decrement the latch count
latch.countDown();
}
}
}
public class CountDownLatchExample {
public static void main(String[] args) {
int numberOfWorkers = 3;
CountDownLatch latch = new CountDownLatch(numberOfWorkers);
for (int i = 0; i < numberOfWorkers; i++) {
new Thread(new Worker(latch)).start();
}
try {
// Wait for all workers to finish
latch.await();
System.out.println("All workers have finished.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
In this example, we create a CountDownLatch
with a count of 3. Each worker thread decrements the count when it finishes working. The main thread waits until the count reaches zero.
finally
block to ensure that they are released even if an exception occurs.InterruptedException
and BrokenBarrierException
properly. These exceptions can occur if a thread is interrupted or if the barrier is broken.CyclicBarrier
and CountDownLatch
based on whether you need a reusable barrier or a one - time use barrier.Semaphores and barriers are powerful synchronization tools in Java’s multithreading toolkit. Semaphores are used to control access to limited resources, while barriers are used to synchronize multiple threads at a common point in their execution. By understanding their fundamental concepts, usage methods, common practices, and best practices, developers can write more efficient and reliable multithreaded Java applications.