Using Semaphore in Java Threads

Semaphores in Java, provided by the java.util.concurrent package, are used to control access to a shared resource by multiple threads.
They can be used to implement resource pools or to impose a bound on a collection. A common use case is limiting the number of concurrent threads accessing a particular resource.

Let's look at a practical example where a semaphore is used to control access to a resource pool. We'll create a scenario with a fixed number of resources and multiple threads trying to access these resources.

SemaphoreExample.java
import java.util.concurrent.Semaphore;
 
public class SemaphoreExample {
 
    // Semaphore with 3 permits
    private static final Semaphore semaphore = new Semaphore(3);
 
    static class SharedResource {
        public void use() {
            try {
                System.out.println(Thread.currentThread().getName() + " is using the resource.");
                Thread.sleep(1000); // Simulating resource use
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                System.out.println(Thread.currentThread().getName() + " is done using the resource.");
            }
        }
    }
 
    static class Task implements Runnable {
        private SharedResource resource;
 
        public Task(SharedResource resource) {
            this.resource = resource;
        }
 
        @Override
        public void run() {
            try {
                // Acquiring the semaphore
                semaphore.acquire();
                resource.use();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                // Releasing the semaphore
                semaphore.release();
            }
        }
    }
 
    public static void main(String[] args) {
        SharedResource resource = new SharedResource();
 
        // Creating and starting five threads
        for (int i = 0; i < 5; i++) {
            new Thread(new Task(resource), "Thread " + (i + 1)).start();
        }
    }
}

Semaphore Creation: A Semaphore is instantiated with 3 permits, meaning it allows up to 3 threads to use the resource concurrently.

SharedResource Class: Represents a resource that multiple threads will try to use. The use method simulates the resource usage.

Task Class: Implements Runnable and represents a task that will use the SharedResource. It acquires a permit from the semaphore before using the resource and releases the permit afterward.

Main Method: Creates and starts five threads. Each thread attempts to use the shared resource. Since the semaphore has only 3 permits.

In this example, the semaphore acts as a gatekeeper, controlling how many threads can access the shared resource at the same time. This is particularly useful for resource management in concurrent applications, where uncontrolled access could lead to issues like resource exhaustion or contention.

The semaphore provides a straightforward way to implement such control, ensuring that only a limited number of threads can use the resource concurrently, thus maintaining system stability and performance.