Without the use of the semaphore, accesses toShared.countby both threads would have
occurred simultaneously, and the increments and decrements would be intermixed. To confirm
this, try commenting out the calls toacquire( )andrelease( ). When you run the program,
you will see that access toShared.countis no longer synchronized, and each thread accesses
it as soon as it gets a timeslice.
Although many uses of a semaphore are as straightforward as that shown in the preceding
program, more intriguing uses are also possible. Here is an example. The following program
reworks the producer/consumer program shown in Chapter 11 so that it uses two semaphores
to regulate the producer and consumer threads, ensuring that each call toput( )is followed
by a corresponding call toget( ):
// An implementation of a producer and consumer
// that use semaphores to control synchronization.
import java.util.concurrent.Semaphore;
class Q {
int n;
// Start with consumer semaphore unavailable.
static Semaphore semCon = new Semaphore(0);
static Semaphore semProd = new Semaphore(1);
void get() {
try {
semCon.acquire();
} catch(InterruptedException e) {
System.out.println("InterruptedException caught");
}
System.out.println("Got: " + n);
semProd.release();
}
void put(int n) {
try {
semProd.acquire();
} catch(InterruptedException e) {
System.out.println("InterruptedException caught");
}
this.n = n;
System.out.println("Put: " + n);
semCon.release();
}
}
class Producer implements Runnable {
Q q;
Producer(Q q) {
this.q = q;
Chapter 26: The Concurrency Utilities 793