211. Semaphores

A semaphore is a Java synchronizer that allows us to control the number of threads that can access a resource at any one time. Conceptually, this synchronizer manages a bunch of permits (for example, similar to tokens). A thread that needs access to the resource must acquire a permit from the synchronizer. After the thread finishes its job with the resource, it must release the permit by returning it to the semaphore so that another thread can acquire it. A thread can acquire a permit immediately (if a permit is free), can wait for a certain amount of time, or can wait until a permit becomes free. Moreover, a thread can acquire and release more than one permit at a time, and a thread can release a permit even if it did not acquire one. This will add a permit to the semaphore; therefore a semaphore can start with one number of permits and die with another.

In API terms, this synchronizer is represented by java.util.concurrent.Semaphore.

Creating a Semaphore is as easy as calling one of its two constructors:

  • public Semaphore​(int permits)
  • public Semaphore​(int permits, boolean fair)

A fair Semaphore guarantees FIFO granting of permits under contention.

Acquiring a permit can be accomplished using the acquire() method. The process can be represented by the following bullets:

  • Without arguments, this method will acquire a permit from this semaphore, blocking until one is available, or the thread is interrupted
  • To acquire more than one permit, use acquire​(int permits)
  • To try to acquire a permit and return a flag value immediately, use tryAcquire() or tryAcquire​(int permits)
  • To acquire a permit by waiting for one to become available within the given waiting time (and the current thread has not been interrupted), use tryAcquire​(int permits, long timeout, TimeUnit unit)
  • To acquire a permit from this semaphore, blocking until one is available can be obtained via acquireUninterruptibly() and acquireUninterruptibly(int permits)
  • To release a permit, use release()

Now, in our scenario, a barbershop has three seats and serves the customers in a FIFO manner. A customer tries for five seconds to take a seat. In the end, it releases the acquired seat. Check out the following code to see how a seat can be acquired and released:

public class Barbershop {

private static final Logger logger =
Logger.getLogger(Barbershop.class.getName());

private final Semaphore seats;

public Barbershop(int seatsCount) {
this.seats = new Semaphore(seatsCount, true);
}

public boolean acquireSeat(int customerId) {
logger.info(() -> "Customer #" + customerId
+ " is trying to get a seat");

try {
boolean acquired = seats.tryAcquire(
5 * 1000, TimeUnit.MILLISECONDS);

if (!acquired) {
logger.info(() -> "Customer #" + customerId
+ " has left the barbershop");

return false;
}

logger.info(() -> "Customer #" + customerId + " got a seat");

return true;
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
logger.severe(() -> "Exception: " + ex);
}

return false;
}

public void releaseSeat(int customerId) {
logger.info(() -> "Customer #" + customerId
+ " has released a seat");
seats.release();
}
}

If no seat has been freed in these five seconds, the person leaves the barber shop. On the other hand, a customer that succeeds in taking a seat is served by a barber (this will take a random number of seconds between 0 and 10). Finally, the customer releases the seat. In code lines, this can be written as follows:

public class BarbershopCustomer implements Runnable {

private static final Logger logger =
Logger.getLogger(BarbershopCustomer.class.getName());
private static final Random rnd = new Random();

private final Barbershop barbershop;
private final int customerId;

public BarbershopCustomer(Barbershop barbershop, int customerId) {
this.barbershop = barbershop;
this.customerId = customerId;
}

@Override
public void run() {

boolean acquired = barbershop.acquireSeat(customerId);

if (acquired) {
try {
Thread.sleep(rnd.nextInt(10 * 1000));
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
logger.severe(() -> "Exception: " + ex);
} finally {
barbershop.releaseSeat(customerId);
}
} else {
Thread.currentThread().interrupt();
}
}
}

Let's bring 10 customers to our barbershop:

Barbershop bs = new Barbershop(3);

for (int i = 1; i <= 10; i++) {
BarbershopCustomer bc = new BarbershopCustomer(bs, i);
new Thread(bc).start();
}

Here is a snapshot of a possible output:

[16:36:17] [INFO] Customer #10 is trying to get a seat
[16:36:17] [INFO] Customer #5 is trying to get a seat
[16:36:17] [INFO] Customer #7 is trying to get a seat
[16:36:17] [INFO] Customer #5 got a seat
[16:36:17] [INFO] Customer #10 got a seat
[16:36:19] [INFO] Customer #10 has released a seat
...
A permit is not acquired on a thread basis.

This means that the T1 thread can acquire a permit from a Semaphore and the T2 thread can release it. Of course, the developer is responsible for managing the process.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset