Interthread Communication
Consider
the classic queuing problem, where one thread is producing some data and
another is consuming it. To make the problem more interesting, suppose that the
producer has to wait until the consumer is finished before it generates more
data.
In a
polling system, the consumer would waste many CPU cycles while it waited for
the producer to produce. Once the producer was finished, it would start
polling, wasting more CPU cycles waiting for the consumer to finish, and so on.
Clearly, this situation is undesirable.
To avoid
polling, Java includes an elegant interprocess communication mechanism via the
following methods:
wait(
): This method tells the calling thread to give up the monitor and go to
sleep until some other thread enters the same monitor and calls notify( ).
notify(
): This method wakes up the first thread that called wait( ) on the same
object.
notifyAll(
): This method wakes up all the threads that called wait( ) on the same
object. The highest priority thread will run first.
These
methods are implemented as final methods in Object, so all classes
have them. All three methods can be called only from within
a synchronized context.
These
methods are declared within Object. Various forms of wait( ) exist that allow
you to specify a period of time to wait.
Example:
The
following sample program consists of four classes: Q, the queue that you're
trying to synchronize; Producer, the threaded object that is producing queue
entries; Consumer, the threaded object that is consuming queue entries; and PC,
the tiny class that creates the single Q, Producer, and Consumer.
The proper
way to write this program in Java is to use wait( ) and notify( ) to signal in
both directions, as shown here:
|
class Q {
int n;
boolean valueSet = false;
synchronized int get() {
if(!valueSet)
try {
wait();
} catch(InterruptedException e) {
System.out.println("InterruptedException
caught");
}
System.out.println("Got: " +
n);
valueSet = false;
notify();
return n;
}
synchronized void put(int n) {
if(valueSet)
try {
wait();
} catch(InterruptedException e) {
System.out.println("InterruptedException caught");
}
this.n = n;
valueSet = true;
System.out.println("Put: " +
n);
notify();
}
}
class Producer
implements Runnable {
Q q;
Producer(Q q) {
this.q = q;
new Thread(this,
"Producer").start();
}
public void run() {
int i = 0;
while(true) {
q.put(i++);
}
}
}
class Consumer
implements Runnable {
Q q;
Consumer(Q q) {
this.q = q;
new Thread(this,
"Consumer").start();
}
public void run() {
while(true) {
q.get();
}
}
}
class PCFixed {
public static void main(String args[]) {
Q q = new Q();
new Producer(q);
new Consumer(q);
System.out.println("Press Control-C to
stop.");
}
}
|
Inside get(
), wait( ) is called. This causes its execution to suspend until the Producer
notifies you that some data is ready.
When this
happens, execution inside get( ) resumes. After the data has been obtained,
get( ) calls notify( ). This tells Producer that it is okay to put more data in
the queue.
Inside put(
), wait( ) suspends execution until the Consumer has removed the item from the
queue. When execution resumes, the next item of data is put in the queue, and
notify( ) is called. This tells the Consumer that it should now remove it.
Here is
some output from this program, which shows the clean synchronous behavior:
|
Put: 1
Got: 1
Put: 2
Got: 2
Put: 3
Got: 3
Put: 4
Got: 4
Put: 5
Got: 5
|
No comments:
Post a Comment