- Why Concurrent Programming?
- Synchronizers: A Semaphore Example
- Concurrent Locks and TimeUnit
- Queues and Executors
- Conclusion
Synchronizers: A Semaphore Example
The classic synchronization problem of protecting critical code execution can be solved using semaphores. In this situation, you have some critical code section that must not be interrupted once it starts. Also, the code must be executed simultaneously by multiple threads only a set number of times. (By simultaneously, I'm referring to execution inside multiple parallel threads of execution.) Listing 1 illustrates the technique.
Listing 1 Protecting critical code with semaphores.
class ProcessExclusion extends Thread { private int threadId; private Semaphore semaphore; public ProcessExclusion(Semaphore semaphore) { this.semaphore = semaphore; } public void setThreadId(int threadId) { this.threadId = threadId; } private int random(int n) { return (int) Math.round(n * Math.random()); } private void busyCode() { int sleepPeriod = random(500); try { sleep(sleepPeriod); } catch (InterruptedException e) { } } private void noncriticalCode() { busyCode(); } private void criticalCode() { busyCode(); } public void run() { for (int i = 0; i < 3; i++) { try { semaphore.acquire(); criticalCode(); semaphore.release(); } catch (InterruptedException e) { System.out.println("Exception " + e.toString()); } } for (int i = 0; i < 3; i++) { noncriticalCode(); } } public static void main(String[] args) { final int numberOfProcesses = 3; final int numberOfPermits = 2; Semaphore semaphore = new Semaphore(numberOfPermits, true); ProcessExclusion p[] = new ProcessExclusion[numberOfProcesses]; for (int i = 0; i < numberOfProcesses; i++) { p[i] = new ProcessExclusion(semaphore); p[i].setThreadId(p[i].hashCode()); p[i].start(); } } }
Listing 1 has two main code sections: noncriticalCode() and criticalCode(). The requirement is that our critical code is simultaneously executed no more than two times. For this reason, invocations of criticalCode() are delimited with calls to acquire() and then release() our semaphore object. The semaphore object is instantiated as follows:
final int numberOfPermits = 2; Semaphore semaphore = new Semaphore(numberOfPermits, true);
This means that the semaphore object has two slots; that is, you can make two calls, one after the other, to semaphore.acquire(). However, if you make a third call without first calling semaphore.release(), your thread will be suspended. To test this result, simply comment out the second call to semaphore.release() in the run() method in Listing 1. If you try this, execution simply stops, because the JVM is waiting for the semaphore to be released. For reasons like this, semaphores must be used with caution, but they're a very effective way of protecting critical code execution. A more flexible option may be found in the classes that implement the Lock interface, and the following section examines this possibility.