Semaphore in Java: A Comprehensive Guide
When working with multithreaded applications in Java, controlling access to shared resources is crucial to avoid race conditions and ensure proper synchronization. One of the key concurrency utilities in Java is the Semaphore, which helps regulate access to resources by using permits. In this article, we will dive deep into the concept of semaphores, their types, usage, and implementation in Java.
What is a Semaphore?
A semaphore is a synchronization mechanism that controls the number of threads that can access a particular resource at the same time. It maintains a set number of permits, and threads must acquire a permit before proceeding with execution. If no permits are available, the thread must wait until one is released.
Semaphores are commonly used in scenarios where we need to limit concurrent access to a finite number of resources, such as database connections, network sockets, or file handles.
Types of Semaphores
Semaphores are broadly classified into two types:
-
Counting Semaphore: This type of semaphore allows multiple threads to access a limited number of resources. The count value determines the number of concurrent threads allowed.
-
Binary Semaphore: This is a special case of the counting semaphore where the count is either 0 or 1, making it similar to a mutex (mutual exclusion lock). It is useful for controlling access to a single resource.
Java's Semaphore Class
Java provides the Semaphore
class in the java.util.concurrent
package, which facilitates thread synchronization.
Key Methods of the Semaphore Class:
-
Semaphore(int permits)
: Creates a semaphore with the given number of permits. -
Semaphore(int permits, boolean fair)
: Creates a semaphore with a fairness policy. -
void acquire()
: Acquires a permit, blocking if none are available. -
void acquire(int permits)
: Acquires the specified number of permits. -
void release()
: Releases a permit, increasing the count. -
void release(int permits)
: Releases the specified number of permits. -
int availablePermits()
: Returns the number of available permits.
Implementing Semaphore in Java
Let's look at an example where multiple threads try to access a shared resource, but only a limited number of them are allowed at the same time.
import java.util.concurrent.Semaphore;
class SharedResource {
private Semaphore semaphore;
public SharedResource(int permits) {
this.semaphore = new Semaphore(permits);
}
public void accessResource(String threadName) {
try {
System.out.println(threadName + " is trying to acquire a permit.");
semaphore.acquire();
System.out.println(threadName + " acquired a permit. Processing...");
Thread.sleep(2000); // Simulate processing time
System.out.println(threadName + " has finished processing and is releasing a permit.");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
}
}
public class SemaphoreExample {
public static void main(String[] args) {
SharedResource resource = new SharedResource(2); // Only 2 threads can access at a time
Runnable task = () -> {
String threadName = Thread.currentThread().getName();
resource.accessResource(threadName);
};
for (int i = 1; i <= 5; i++) {
Thread thread = new Thread(task, "Thread-" + i);
thread.start();
}
}
}
Output Example:
Thread-1 is trying to acquire a permit.
Thread-2 is trying to acquire a permit.
Thread-1 acquired a permit. Processing...
Thread-2 acquired a permit. Processing...
Thread-3 is trying to acquire a permit.
Thread-4 is trying to acquire a permit.
Thread-5 is trying to acquire a permit.
Thread-1 has finished processing and is releasing a permit.
Thread-3 acquired a permit. Processing...
Thread-2 has finished processing and is releasing a permit.
Thread-4 acquired a permit. Processing...
Fair vs. Non-Fair Semaphores
Semaphores can be configured to follow a fair or non-fair policy:
-
Fair Semaphore: Ensures that threads acquire permits in a first-come, first-served order.
-
Non-Fair Semaphore: Allows any waiting thread to acquire a permit, potentially leading to thread starvation.
Example of a fair semaphore:
Semaphore fairSemaphore = new Semaphore(3, true); // Enabling fairness
Advanced Example: Using Semaphores with ExecutorService
In large-scale applications, using semaphores with an ExecutorService
can help manage thread pools efficiently. Below is an example:
import java.util.concurrent.*;
class Task implements Runnable {
private Semaphore semaphore;
private String taskName;
public Task(Semaphore semaphore, String taskName) {
this.semaphore = semaphore;
this.taskName = taskName;
}
@Override
public void run() {
try {
System.out.println(taskName + " is waiting for a permit.");
semaphore.acquire();
System.out.println(taskName + " acquired a permit and is executing.");
Thread.sleep(2000); // Simulating work
System.out.println(taskName + " is releasing the permit.");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
}
}
public class SemaphoreExecutorExample {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3); // Limit to 3 concurrent tasks
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 1; i <= 10; i++) {
executorService.submit(new Task(semaphore, "Task-" + i));
}
executorService.shutdown();
}
}
Expected Output:
Task-1 is waiting for a permit.
Task-2 is waiting for a permit.
Task-3 is waiting for a permit.
Task-1 acquired a permit and is executing.
Task-2 acquired a permit and is executing.
Task-3 acquired a permit and is executing.
...
This approach ensures efficient handling of concurrent tasks while controlling access to limited resources.
Use Cases of Semaphores
Semaphores are used in various real-world scenarios, including:
-
Controlling access to a limited number of resources (e.g., database connections, API rate limiting).
-
Implementing bounded thread pools (e.g., limiting concurrent worker threads).
-
Preventing thread starvation in heavily multi-threaded environments.
Conclusion
Semaphores in Java provide a powerful way to control concurrent access to resources. By leveraging the Semaphore
class, developers can effectively manage synchronization and prevent race conditions in multithreaded applications. Understanding when and how to use semaphores can greatly enhance the efficiency and reliability of your Java programs.
Sign up here with your email
ConversionConversion EmoticonEmoticon