Semaphore in Java

Use `Semaphore` in Java to bound concurrent access to scarce resources and make admission control explicit.

10.3.3.3 Semaphore

Introduction to Semaphores

In the realm of concurrent programming, managing access to shared resources is crucial to ensure data integrity and application stability. A Semaphore is a synchronization construct that controls access to a shared resource by maintaining a set of permits. It is a part of the java.util.concurrent package, which provides high-level concurrency utilities in Java.

Semaphores can be visualized as counters that regulate the number of threads that can access a particular resource simultaneously. When a thread wants to access the resource, it must acquire a permit from the semaphore. If no permits are available, the thread is blocked until one becomes available. Once the thread is done with the resource, it releases the permit back to the semaphore.

Historical Context

The concept of semaphores was introduced by Edsger Dijkstra in the 1960s as a means to solve synchronization problems in operating systems. Initially, semaphores were used to manage access to critical sections and prevent race conditions. Over time, they have evolved to become a fundamental tool in concurrent programming, especially in environments where resource management is critical.

Semaphore in Java

Java’s Semaphore class provides a robust mechanism for managing concurrent access to resources. It supports two main operations:

  • Acquire: A thread requests a permit to access the resource. If a permit is available, the semaphore decrements the count and allows the thread to proceed. If no permits are available, the thread is blocked until one becomes available.
  • Release: A thread returns a permit to the semaphore, incrementing the count and potentially unblocking a waiting thread.

Key Features of Java’s Semaphore

  • Fairness: Semaphores can be configured to be fair or non-fair. A fair semaphore ensures that threads acquire permits in the order they requested them, using a first-in-first-out (FIFO) queue. A non-fair semaphore does not guarantee this order, which can lead to higher throughput but may cause starvation.
  • Permits: The number of permits determines how many threads can access the resource concurrently. This can be set during the semaphore’s initialization.

Practical Applications

Semaphores are particularly useful in scenarios where you need to limit the number of concurrent accesses to a resource. Common use cases include:

  • Limiting Concurrent Database Connections: In a web application, you might want to limit the number of simultaneous connections to a database to prevent overloading it.
  • Controlling Access to a Pool of Resources: For example, managing a pool of network connections or threads.
  • Rate Limiting: Ensuring that a certain operation is performed only a specified number of times per second.

Semaphore Usage in Java

Let’s explore how to use semaphores in Java with practical examples.

Basic Semaphore Example

 1import java.util.concurrent.Semaphore;
 2
 3public class SemaphoreExample {
 4    private static final int MAX_PERMITS = 3;
 5    private final Semaphore semaphore = new Semaphore(MAX_PERMITS);
 6
 7    public void accessResource() {
 8        try {
 9            // Acquire a permit
10            semaphore.acquire();
11            System.out.println(Thread.currentThread().getName() + " acquired a permit.");
12
13            // Simulate resource access
14            Thread.sleep(1000);
15
16        } catch (InterruptedException e) {
17            Thread.currentThread().interrupt();
18        } finally {
19            // Release the permit
20            semaphore.release();
21            System.out.println(Thread.currentThread().getName() + " released a permit.");
22        }
23    }
24
25    public static void main(String[] args) {
26        SemaphoreExample example = new SemaphoreExample();
27
28        // Create multiple threads to access the resource
29        for (int i = 0; i < 10; i++) {
30            new Thread(example::accessResource).start();
31        }
32    }
33}

Explanation: In this example, a semaphore with three permits is created. Ten threads attempt to access the resource, but only three can do so concurrently. The rest must wait until a permit is released.

Fair vs. Non-Fair Semaphore

By default, semaphores in Java are non-fair. To create a fair semaphore, you can pass true as the second argument to the constructor.

1Semaphore fairSemaphore = new Semaphore(MAX_PERMITS, true);

Fair Semaphore Example:

 1import java.util.concurrent.Semaphore;
 2
 3public class FairSemaphoreExample {
 4    private static final int MAX_PERMITS = 3;
 5    private final Semaphore semaphore = new Semaphore(MAX_PERMITS, true);
 6
 7    public void accessResource() {
 8        try {
 9            semaphore.acquire();
10            System.out.println(Thread.currentThread().getName() + " acquired a permit.");
11
12            Thread.sleep(1000);
13
14        } catch (InterruptedException e) {
15            Thread.currentThread().interrupt();
16        } finally {
17            semaphore.release();
18            System.out.println(Thread.currentThread().getName() + " released a permit.");
19        }
20    }
21
22    public static void main(String[] args) {
23        FairSemaphoreExample example = new FairSemaphoreExample();
24
25        for (int i = 0; i < 10; i++) {
26            new Thread(example::accessResource).start();
27        }
28    }
29}

Explanation: In this fair semaphore example, threads acquire permits in the order they requested them, ensuring fairness.

Advanced Semaphore Usage

Semaphore with Timeout

Sometimes, a thread may need to acquire a permit within a certain timeframe. Java’s Semaphore class provides a method tryAcquire that allows specifying a timeout.

 1boolean acquired = semaphore.tryAcquire(500, TimeUnit.MILLISECONDS);
 2if (acquired) {
 3    try {
 4        // Access the resource
 5    } finally {
 6        semaphore.release();
 7    }
 8} else {
 9    System.out.println("Could not acquire a permit within the timeout.");
10}

Explanation: This code attempts to acquire a permit within 500 milliseconds. If successful, the thread proceeds; otherwise, it handles the timeout scenario.

Semaphore for Resource Pools

Semaphores are ideal for managing a pool of resources, such as a connection pool. Here’s a simplified example:

 1import java.util.concurrent.Semaphore;
 2import java.util.ArrayList;
 3import java.util.List;
 4
 5public class ConnectionPool {
 6    private final List<Connection> pool;
 7    private final Semaphore semaphore;
 8
 9    public ConnectionPool(int size) {
10        pool = new ArrayList<>(size);
11        semaphore = new Semaphore(size);
12        for (int i = 0; i < size; i++) {
13            pool.add(new Connection("Connection-" + i));
14        }
15    }
16
17    public Connection acquireConnection() throws InterruptedException {
18        semaphore.acquire();
19        return getConnectionFromPool();
20    }
21
22    public void releaseConnection(Connection connection) {
23        returnConnectionToPool(connection);
24        semaphore.release();
25    }
26
27    private synchronized Connection getConnectionFromPool() {
28        return pool.remove(pool.size() - 1);
29    }
30
31    private synchronized void returnConnectionToPool(Connection connection) {
32        pool.add(connection);
33    }
34
35    public static void main(String[] args) {
36        ConnectionPool pool = new ConnectionPool(3);
37
38        for (int i = 0; i < 10; i++) {
39            new Thread(() -> {
40                try {
41                    Connection connection = pool.acquireConnection();
42                    System.out.println(Thread.currentThread().getName() + " acquired " + connection.getName());
43                    Thread.sleep(1000);
44                    pool.releaseConnection(connection);
45                    System.out.println(Thread.currentThread().getName() + " released " + connection.getName());
46                } catch (InterruptedException e) {
47                    Thread.currentThread().interrupt();
48                }
49            }).start();
50        }
51    }
52}
53
54class Connection {
55    private final String name;
56
57    public Connection(String name) {
58        this.name = name;
59    }
60
61    public String getName() {
62        return name;
63    }
64}

Explanation: This example demonstrates a connection pool managed by a semaphore. The pool allows only a limited number of connections to be acquired simultaneously.

Best Practices and Considerations

  • Fairness vs. Throughput: Choose fair semaphores when fairness is critical, but be aware that they may reduce throughput due to the overhead of maintaining a queue.
  • Avoid Deadlocks: Ensure that permits are always released, even in the event of an exception. Use try-finally blocks to guarantee this.
  • Resource Management: Use semaphores to manage finite resources effectively, preventing resource exhaustion and ensuring stability.

Common Pitfalls

  • Permit Leaks: Failing to release a permit can lead to deadlocks or resource starvation. Always ensure permits are released.
  • Overuse of Fairness: While fairness is important, overusing fair semaphores can lead to performance bottlenecks. Evaluate the trade-offs carefully.
  • Incorrect Initialization: Initializing a semaphore with an incorrect number of permits can lead to unexpected behavior. Ensure the permit count matches the available resources.

Conclusion

Semaphores are a powerful tool for managing concurrency in Java applications. By controlling access to shared resources, they help prevent race conditions and ensure application stability. Understanding how to use semaphores effectively is essential for any Java developer working with multithreaded applications.

Known Uses

  • Java’s Executors Framework: Uses semaphores internally to manage thread pools.
  • Database Connection Pools: Many database connection pool implementations use semaphores to limit concurrent connections.

Further Reading

Test Your Knowledge: Java Semaphore Concurrency Quiz

Loading quiz…

By understanding and effectively utilizing semaphores, Java developers can enhance the concurrency control in their applications, ensuring efficient and safe access to shared resources.

Revised on Thursday, April 23, 2026