Semaphore — Concurrency Control
Control concurrent access with Semaphore: rate limiting, resource pools, and bounded parallelism.
import java.util.concurrent.*;
import java.util.stream.IntStream;
public class SemaphoreDemo {
// Limit concurrent access to a resource
static class BoundedService {
private final Semaphore semaphore;
BoundedService(int maxConcurrent) {
this.semaphore = new Semaphore(maxConcurrent);
}
String process(String request) throws InterruptedException {
semaphore.acquire();
try {
System.out.printf("[%s] Processing: %s (permits: %d)%n",
Thread.currentThread().getName(), request,
semaphore.availablePermits());
Thread.sleep(1000); // simulate work
return "Result: " + request;
} finally {
semaphore.release();
}
}
// Try with timeout
String tryProcess(String request, long timeoutMs)
throws InterruptedException, TimeoutException {
if (!semaphore.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) {
throw new TimeoutException("Service busy");
}
try {
Thread.sleep(500);
return "Result: " + request;
} finally {
semaphore.release();
}
}
}
// Bounded parallel executor
static class ParallelExecutor {
private final Semaphore semaphore;
private final ExecutorService executor;
ParallelExecutor(int maxParallel) {
this.semaphore = new Semaphore(maxParallel);
this.executor = Executors.newCachedThreadPool();
}
<T> CompletableFuture<T> submit(Callable<T> task) {
return CompletableFuture.supplyAsync(() -> {
try {
semaphore.acquire();
try {
return task.call();
} finally {
semaphore.release();
}
} catch (Exception e) {
throw new CompletionException(e);
}
}, executor);
}
void shutdown() { executor.shutdown(); }
}
public static void main(String[] args) throws Exception {
// Only 3 concurrent requests allowed
BoundedService service = new BoundedService(3);
ExecutorService pool = Executors.newFixedThreadPool(10);
// Submit 10 requests — only 3 process at a time
var futures = IntStream.range(0, 10)
.mapToObj(i -> pool.submit(() -> service.process("req-" + i)))
.toList();
for (var f : futures) {
System.out.println(f.get());
}
// Bounded parallel executor
ParallelExecutor exec = new ParallelExecutor(2);
var results = IntStream.range(0, 5)
.mapToObj(i -> exec.submit(() -> {
Thread.sleep(500);
return "Task-" + i + " done";
}))
.toList();
results.forEach(f -> {
try { System.out.println(f.get()); }
catch (Exception e) { e.printStackTrace(); }
});
pool.shutdown();
exec.shutdown();
}
}Use Cases
- Limiting concurrent API calls to external services
- Resource access control in multi-threaded applications
- Bounded parallelism for batch processing
Tags
Related Snippets
Similar patterns you can reuse in the same workflow.
Concurrent Collections — Thread-Safe Maps
Use ConcurrentHashMap, CopyOnWriteArrayList, and BlockingQueue for thread-safe data structures.
Best for: Thread-safe caching in multi-threaded applications
CompletableFuture — Async Programming
Chain async operations with CompletableFuture: thenApply, thenCompose, allOf, and exception handling.
Best for: Parallel API calls to multiple services
Virtual Threads — Lightweight Concurrency
Use Java 21 virtual threads for massive concurrency without thread pool tuning or reactive frameworks.
Best for: High-concurrency web servers handling thousands of requests
Singleton Pattern — Thread-Safe Approaches
Implement thread-safe singletons in Java: enum, holder class, double-checked locking, and eager init.
Best for: Application-wide configuration managers