javaintermediate

Semaphore — Concurrency Control

Control concurrent access with Semaphore: rate limiting, resource pools, and bounded parallelism.

java
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.