How do I control access to resources using Semaphore?

To control access to shared resources in a multithreaded environment, a Semaphore is frequently used, which is part of the java.util.concurrent package. A semaphore manages a set number of permits that control how many threads can access a shared resource simultaneously. Threads acquire permits before accessing the resource and release the permits after they are done, ensuring controlled and synchronized access.

Here’s how you can use a semaphore to control access to resources:

1. Key Points About Semaphore:

  • Permits: The semaphore holds a set number of permits, which represent the number of threads that can access the resource concurrently.
  • Acquire/Release:
    • A thread must acquire a permit using the acquire() method to access the resource.
    • It must release the permit using release() after finishing its access to the resource.
  • Blocking Behavior: If no permits are available, the acquiring thread will block until a permit is released by another thread.

  • Fairness: You can construct a semaphore in a fair mode to ensure that waiting threads acquire permits in the order they requested them.

2. Example: Semaphore with Limited Access to Resources

Here is a simple example where a semaphore is used to control access to a shared resource (e.g., a connection pool or a printer):

package org.kodejava.util.concurrent;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

public class SemaphoreExample {

    // Semaphore initialized with 2 permits (only 2 threads can access simultaneously).
    private static final Semaphore semaphore = new Semaphore(2);

    public static void main(String[] args) {
        // Create a thread pool with 5 threads
        ExecutorService executorService = Executors.newFixedThreadPool(5);

        // Simulate 5 threads trying to access the shared resource
        for (int i = 1; i <= 5; i++) {
            final int threadId = i;
            executorService.submit(() -> {
                try {
                    // Try to acquire a permit
                    System.out.println("Thread " + threadId + " is trying to acquire a permit.");
                    semaphore.acquire();  // Blocks if no permit is available

                    // Access the shared resource
                    System.out.println("Thread " + threadId + " has acquired a permit.");
                    Thread.sleep(2000);  // Simulate the resource usage

                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } finally {
                    // Release the permit after use
                    System.out.println("Thread " + threadId + " is releasing the permit.");
                    semaphore.release();
                }
            });
        }

        executorService.shutdown();
    }
}

3. How It Works:

  1. Initialization: The semaphore is initialized with a number of permits (new Semaphore(2)), allowing only 2 threads to access the resource concurrently.
  2. Acquire: A thread attempts to access the resource by calling semaphore.acquire(). If permits are unavailable, the thread is blocked until a permit is released by another thread.
  3. Critical Section: Once the permit is acquired, it enters the critical section and uses the resource.
  4. Release: After the thread is done using the resource, it calls semaphore.release() to return a permit, allowing other threads to acquire it.

4. Output Example:

When you run the example above, you might see an output like this, showing how only 2 threads can access the resource simultaneously:

Thread 1 is trying to acquire a permit.
Thread 1 has acquired a permit.
Thread 2 is trying to acquire a permit.
Thread 2 has acquired a permit.
Thread 3 is trying to acquire a permit.
Thread 1 is releasing the permit.
Thread 3 has acquired a permit.
Thread 4 is trying to acquire a permit.
Thread 2 is releasing the permit.
Thread 4 has acquired a permit.
Thread 5 is trying to acquire a permit.
Thread 3 is releasing the permit.
Thread 5 has acquired a permit.
Thread 4 is releasing the permit.
Thread 5 is releasing the permit.

Here, only 2 threads are allowed to acquire permits at a time, while others are blocked until permits are released.

5. Fair Ordering:

If you want the semaphore to provide fairness (FIFO order), you can use the constructor:

Semaphore semaphore = new Semaphore(2, true);

The second argument (true) enables fair ordering, making sure the threads acquire permits in the order they requested them.

6. Use Cases:

  • Database Connection Pools: Managing the number of simultaneous connections to a database.
  • Printers: Limiting how many jobs can access a shared printer.
  • Rate Limiting: Throttling the number of threads processing tasks in high-volume systems.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.