The Producer
generates an integer between 0 and 9 (inclusive), stores it in a CubbyHole
object, and prints the generated number. To make the synchronization problem more interesting, the Producer
sleeps for a random amount of time between 0 and 100 milliseconds before repeating the number generating cycle. Lets see what producer produces:
CubbyHole.java – Consumer product
public class CubbyHole {
private int contents;
private boolean available = false;
//get and set methods are sync to put locks
// on resource
public synchronized int get() {
while (available == false) {
try {
wait();
} catch (InterruptedException e) { }
}
available = false;
notifyAll();
return contents;
}
public synchronized void put(int value) {
while (available == true) {
try {
wait();
} catch (InterruptedException e) { }
}
contents = value;
available = true;
notifyAll();
}
}
public class Producer extends Thread {
private CubbyHole cubbyhole;
private int number;
public Producer(CubbyHole c, int number) {
cubbyhole = c;
this.number = number;
}
public void run() {
for (int i = 0; i < 10; i++) {
cubbyhole.put(i);
System.out.println("Producer #"
+ this.number + " put: " + i);
try {
sleep((int)(Math.random() * 100));
}
catch (InterruptedException e) {
// do something
}
}
}
}
The
Consumer
, being ravenous, consumes all integers from the CubbyHole
(the exact same object into which the Producer
put the integers in the first place) as quickly as they become available. public class Consumer extends Thread {
private CubbyHole cubbyhole;
private int number;
public Consumer(CubbyHole c, int number) {
cubbyhole = c;
this.number = number;
}
public void run() {
int value = 0;
for (int i = 0; i < 10; i++) {
value = cubbyhole.get();
System.out.println("Consumer #" +
this.number + " got: " + value);
}
}
}
The
Producer
and Consumer
in this example share data through a common CubbyHole
object. And you will note that neither the Producer
nor the Consumer
makes any effort whatsoever to ensure that the Consumer
is getting each value produced once and only once. The synchronization between these two threads actually occurs at a lower level, within the get
and put
methods of the CubbyHole
object. However, let's assume for a moment that these two threads make no arrangements for synchronization and talk about the potential problems that might arise in that situation. One problem arises when the Producer
is quicker than the Consumer
and generates two numbers before the Consumer
has a chance to consume the first one. Thus the Consumer
would skip a number. Part of the output might look like this: . . .Another problem that might arise is when the
Consumer #1 got: 3
Producer #1 put: 4
Producer #1 put: 5
Consumer #1 got: 5
. . .
Consumer
is quicker than the Producer
and consumes the same value twice. In this situation, the Consumer
would print the same value twice and might produce output that looked like this: . . .Either way, the result is wrong. You want the
Producer #1 put: 4
Consumer #1 got: 4
Consumer #1 got: 4
Producer #1 put: 5
. . .
Consumer
to get each integer produced by the Producer
exactly once. Problems such as those just described are called race conditions. They arise from multiple, asynchronously executing threads trying to access a single object at the same time and getting the wrong result. Race conditions in the producer/consumer example are prevented by having the storage of a new integer into the CubbyHole
by the Producer
be synchronized with the retrieval of an integer from the CubbyHole
by the Consumer
. The Consumer
must consume each integer exactly once. The activities of the
Producer
and Consumer
must be synchronized in two ways. First, the two threads must not simultaneously access the CubbyHole
. A Java thread can prevent this from happening by locking an object. When an object is locked by one thread and another thread tries to call a synchronized method on the same object, the second thread will block until the object is unlocked. Locking an Object discusses this. And second, the two threads must do some simple coordination. That is, the
Producer
must have some way to indicate to the Consumer
that the value is ready and the Consumer
must have some way to indicate that the value has been retrieved. The Thread
class provides a collection of methods--wait
, notify
, and notifyAll
--to help threads wait for a condition and notify other threads of when that condition changes. Using the notifyAll and wait Methods has more information. The Main Program
Here's a small stand-alone Java application that creates a CubbyHole
object, a Producer
, a Consumer
, and then starts both the Producer
and the Consumer
.
public class ProducerConsumerTest {
public static void main(String[] args) {
CubbyHole c = new CubbyHole();
Producer p1 = new Producer(c, 1);
Consumer c1 = new Consumer(c, 1);
p1.start();
c1.start();
}
}
The Output
Here's the output of ProducerConsumerTest.Producer #1 put: 0
Consumer #1 got: 0
Producer #1 put: 1
Consumer #1 got: 1
Producer #1 put: 2
Consumer #1 got: 2
Producer #1 put: 3
Consumer #1 got: 3
Producer #1 put: 4
Consumer #1 got: 4
Producer #1 put: 5
Consumer #1 got: 5
Producer #1 put: 6
Consumer #1 got: 6
Producer #1 put: 7
Consumer #1 got: 7
Producer #1 put: 8
Consumer #1 got: 8
Producer #1 put: 9
Consumer #1 got: 9
Download the source-code
Please download the source code from here.
No comments:
Post a Comment