class Counter { private int count = 0; public void Increment() { int n = count; count = n+1; } }What might happen if two threads share an object
Counter counter;and both try to execute
counter.Increment();almost simultaneously? One possible execution scenario (discussed in class):
Thread 1 | Thread 2 | |
counter.Increment(); | ||
n = count; // 0 | ||
counter.Increment(); | ||
n = count; // 0 | ||
count = n + 1; // 1 | ||
count = n + 1; // 1 |
We can avoid this situation by declaring the Increment method to be synchronized, as in
class Counter { private int count = 0; public void synchronized Increment() { int n = count; count = n+1; } }The revised scenario:
Thread 1 | Thread 2 | |
counter.Increment(); | ||
(acquires the monitor) | ||
n = count; // 0 | ||
counter.Increment(); | ||
(can't acquire monitor) | ||
count = n + 1; // 1 | ---(blocked) | |
(releases the monitor) | ---(blocked) | |
(acquires the monitor) | ||
n = count; // 1 | ||
count = n + 1; // 2 | ||
(releases the monitor) |
When Thread 2 attempts to execute the Increment() method on the same counter object, the thread is blocked. Thread 2 is unable to acquire ownership of the counter object's monitor; the monitor is already owned by Thread 1. Thread 2 is suspended until the monitor becomes available. When Thread 1 releases the monitor, Thread 2 is able to acquire the monitor and continue running, completing its call to the method.
Note that Java monitors are not like traditional critical sections. Declaring a method synchronized does not imply that only one thread may execute that method at a time, as would be the case with a critical section. It implies that only one thread may invoke that method (or any synchronized method) on a particular object at any given time. Java monitors are associated with objects, not with blocks of code. Two threads may concurrently execute the same synchronized method, provided that the method is invoked on different objects (that is, a.method() and b.method(), where a != b).
Exercise 1. Download the code PC.java for the producer-consumer problem. The code uses synchronized Get and Put access methods to a shared buffer. Compile and run the code. What happened? Why does this happen?
Now you'll need to kill the running process. Open a new telnet window and type in the following commands at the shell prompt:
bash$ ps -ef | grep your_account_name bash$ kill -9 pidwhere your_account_name is your login name. The first command lists all processes running on the Unix machine that you have created. Identify the PC process and use its identification number pid in the kill command.
We really want the Producer to release the monitor if the buffer becomes full and allow the Consumer to proceed. Similarly, the Consumer must release the monitor if the buffer becomes empty and allow the Producer to proceed. To coordinate the two threads, we must use the Object's wait() and notify or notifyAll() methods.
The wait() method suspends the calling thread and temporarily releases ownership of the monitor (so it allows other threads to acquire the monitor). The suspended thread that called wait() wakes up only when another thread calls notify() or notifyAll() on that object.
The notifyAll() method wakes up all threads waiting on the object in question. The awakened threads compete for the monitor. One thread gets it, and the others go back to waiting.
The notify() method arbitrarily wakes up one of the threads waiting on the object in question. A correct implementation of the Buffer for the producer-consumer problem is given below.
class Buffer { private char [] buffer; private int count = 0, in = 0, out = 0; Buffer(int size) { buffer = new char[size]; } public synchronized void Put(char c) { while(count == buffer.length) { try { wait(); } catch (InterruptedException e) { } finally { } } System.out.println("Producing " + c + " ..."); buffer[in] = c; in = (in + 1) % buffer.length; count++; notify(); } public synchronized char Get() { while (count == 0) { try { wait(); } catch (InterruptedException e) { } finally { } } char c = buffer[out]; out = (out + 1) % buffer.length; count--; System.out.println("Consuming " + c + " ..."); notify(); return c; } }
Important note: A thread may call wait(), notify() or notifyAll() on an object, only if it owns the monitor of that object.
To summarize:
Exercise 2. Make the changes outlined above to the producer-consumer code from exercise 2 and recompile it. It should run successfully this time.
Exercise 5. Study the Java API specifications for semaphore objects and rewrite the producer-consumer code (PC.java) using semaphores this time.