What exactly is a semaphore ?
To use the wiki definition:
A question that came to mind when I started is "Why isn't there a need to synchronize on the semaphore methods ?". The ans : "For what ? "
A more appropriate answer is found in the java doc:
Our semaphores here are not the resource. Instead they are a limiter or a tracker telling code when it is safe to proceed towards a resource. Here our user threads will acquire the semaphore for the duration of their car use. This prevents too many users trying to ask for a car at once. The CarRental object must also use synchronized block while offering or removing cars so as to ensure that if two users ask for a car together they do not receive the same car. Scenarios such as two users receiving the same car to two different threads because they called the method simultaneously.
To use the wiki definition:
In computer science, particularly in operating systems, a semaphore is a variableJava 5 provides us with a Counting Semaphore, that was developed by Doug Lee. I decided to develop a simple example class based on the java docs for the same:
or abstract data type that is used for controlling access, by multiple processes,
to a common resource in a parallel programming or a multi-user environment.
A useful way to think of a semaphore is as a record of how many units of a particular
resource are available, coupled with operations to safely (i.e., without race
conditions) adjust that record as units are required or become free, and, if necessary,
wait until a unit of the resource becomes available.
Semaphores are a useful tool in the prevention of race conditions; however, their
use is by no means a guarantee that a program is free from these problems. Semaphores
which allow an arbitrary resource count are called counting semaphores, while semaphores
which are restricted to the values 0 and 1(or locked/unlocked, unavailable/available)
are called binary semaphores.
class CarRental {The above car rental class has a collection of cars that it rents out to people. It uses semaphores here rather than wait notify to manage the availability of cars. The rent method is as below:
privatestaticfinalint TOTAL_CARS = 3;
private Queue<String> cars = new LinkedList<String>();
{
for (int i = 1; i <= TOTAL_CARS; i++) {
cars.offer("Car No : " + i);
}
}
privatefinalSemaphore availableCars = new Semaphore(TOTAL_CARS, true);
// rent car method
// return rented car method
}
public String rentCar(String user) throws InterruptedException {As seen here to acquire a car, we first acquire a permit from our semaphore. The acquire method used here is a non synchronized method that will only return once we have a permit available. Kind of like a guarded block. So this code would simply block infinitely until a car is available. To prevent the same we have
System.out.println("Car has been requested by " + user + " ...");
long time = System.currentTimeMillis();
availableCars.acquire();// non synchronized but blocking
long blockTime = System.currentTimeMillis() - time;
System.out.println("A car is ready for use - [ " + blockTime + " ms ] ," +
" Retrieve from pool for " + user + " ...");
String car = null;
synchronized (cars) {
car = cars.poll(); // non blocking
}
System.out.println("Car retrieved is " + car + " for user " + user);
return car;
}
availableCars.acquireUninterruptibly();// non synchronized, still blockingThe documentation for the method is:
// but does not throw InterruptedException
If no permit is available then the current thread becomes disabled for thread scheduling purposesAs explained here we get a wait kind of behavior for our thread. This prevents wastage of CPU Time. To proceed with the example here is the returnCar method:
and lies dormant until some other thread invokes the release method for this semaphore and
the current thread is next to be assigned a permit.
If the current thread is interrupted while waiting for a permit then it will continue to wait,
but the time at which the thread is assigned a permit may change compared to the time it would
have received the permit had no interruption occurred. When the thread does return from this
method its interrupt status will be set.
public void returnCar(String car, String user) {As seen above, we first return the car to the pool. Now we release our permit back to the semaphore. This is like sending a notify to all threads that a car is available in the pool. I decided to create Users to fetch cars from the pool:
System.out.println("Car for return is " + car + " by user " + user + " ...");
synchronized (cars) {
cars.offer(car);
}
System.out.println("car [ " + car + " ] added to pool from user " + user);
availableCars.release();// non synchronized non blocking
}
class User implements Runnable {The users as seen get a car from the rental, use it for a certain time and return it once done. Accordingly the main method is as below:
private CarRental carRental;
privatelong carUseTime;
public User(CarRental carRental, long carUseTime) {
this.carRental = carRental;
this.carUseTime = carUseTime;
}
@Override
publicvoid run() {
String user = "[ " + Thread.currentThread().getName() + "]";
String car = carRental.rentCar(user);
// Using car for carUseTime milliseconds
try {
Thread.sleep(carUseTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
// Time to return car
carRental.returnCar(car, user);
}
}
publicstatic void main(String[] args) throws InterruptedException {The output of the code is :
CarRental carRental = new CarRental();
new Thread(new User(carRental, 100), "Fred").start();
new Thread(new User(carRental, 100), "Jane").start();
new Thread(new User(carRental, 100), "Gina").start();
new Thread(new User(carRental, 100), "Roger").start();
}
Car has been requested by [ Fred] ...As seen above the first three users came in and all got their cars immediately. Unfortunately when Jane arrived no cars where there, hence the wait (101 ms) to receive a car.
A car is ready for use - [ 0 ms ] , Retrieve from pool for [ Fred] ...
Car retrieved is Car No : 1 for user [ Fred]
Car has been requested by [ Roger] ...
A car is ready for use - [ 0 ms ] , Retrieve from pool for [ Roger] ...
Car has been requested by [ Gina] ...
Car retrieved is Car No : 2 for user [ Roger]
A car is ready for use - [ 0 ms ] , Retrieve from pool for [ Gina] ...
Car retrieved is Car No : 3 for user [ Gina]
Car has been requested by [ Jane] ...
Car for return is Car No : 1 by user [ Fred] ...
car [ Car No : 1 ] added to pool from user [ Fred]
Car for return is Car No : 3 by user [ Gina] ...
car [ Car No : 3 ] added to pool from user [ Gina]
A car is ready for use - [ 101 ms ] , Retrieve from pool for [ Jane] ...
Car retrieved is Car No : 1 for user [ Jane]
Car for return is Car No : 2 by user [ Roger] ...
car [ Car No : 2 ] added to pool from user [ Roger]
Car for return is Car No : 1 by user [ Jane] ...
car [ Car No : 1 ] added to pool from user [ Jane]
A question that came to mind when I started is "Why isn't there a need to synchronize on the semaphore methods ?". The ans : "For what ? "
A more appropriate answer is found in the java doc:
Note that no synchronization lock is held when acquire() is calledAnother question is "Now that we are using semaphores do we still need to synchronize our Car Pool ?" Yes.
as that would prevent an item from being returned to the pool. The
Semaphore class has been built as a thread safe class which has code
that works similar to the happens before relationship that we get
with the synchronized keyword in java. Actions prior to "releasing"
synchronizer methods such as Semaphore.release happen-before actions
subsequent to a successful "acquiring" method such as Semaphore.acquire
on the same synchronizer object in another thread.
Our semaphores here are not the resource. Instead they are a limiter or a tracker telling code when it is safe to proceed towards a resource. Here our user threads will acquire the semaphore for the duration of their car use. This prevents too many users trying to ask for a car at once. The CarRental object must also use synchronized block while offering or removing cars so as to ensure that if two users ask for a car together they do not receive the same car. Scenarios such as two users receiving the same car to two different threads because they called the method simultaneously.