Semaphore in Java
A Semaphore controls how many threads can access a shared resource at the same time.
Real-World Use Cases
- limiting concurrent outbound API calls
- database connection throttling
- download/upload slot management
- rate-limited integrations
Core Concept
A semaphore has permits:
acquire()takes a permit (waits if none available)release()returns a permit
Java 8 Style Example
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
private static final Semaphore semaphore = new Semaphore(2);
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
final int id = i;
new Thread(() -> runTask(id), "worker-" + id).start();
}
}
private static void runTask(int id) {
try {
semaphore.acquire();
System.out.println("Task " + id + " acquired permit");
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
semaphore.release();
System.out.println("Task " + id + " released permit");
}
}
}
JDK 11 and Java 17 Notes
Semaphore API is stable in JDK 11 and Java 17. The same concurrency-limit pattern is still the recommended approach.
Java 21+ Example (Virtual Threads)
try (var executor = java.util.concurrent.Executors.newVirtualThreadPerTaskExecutor()) {
Semaphore semaphore = new Semaphore(50);
for (int i = 0; i < 10_000; i++) {
int id = i;
executor.submit(() -> {
semaphore.acquire();
try {
// remote call / IO task
} finally {
semaphore.release();
}
return id;
});
}
}
This is useful when you run many lightweight tasks but still need strict external resource limits.
Java 25 Note
Semaphore API remains stable. The same design works; focus on observability and backpressure instead of API migration.
Key Takeaways
- Semaphore is a concurrency limiter, not a queue.
- Always release permits in
finally. - Combine semaphores with retry and timeout logic in production systems.