Doing Two or More Tasks At Once: Threads |
TheProducer
generates an integer between 0 and 9 (inclusive), stores it in aCubbyHole
object, and prints the generated number. To make the synchronization problem more interesting, theProducer
sleeps for a random amount of time between 0 and 100 milliseconds before repeating the number generating cycle:Thepublic 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) { } } } }Consumer
, being ravenous, consumes all integers from theCubbyHole
(the exact same object into which theProducer
put the integers in the first place) as quickly as they become available.Thepublic 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); } } }Producer
andConsumer
in this example share data through a commonCubbyHole
object. And you will note that neither theProducer
nor theConsumer
makes any effort whatsoever to ensure that theConsumer
is getting each value produced once and only once. The synchronization between these two threads actually occurs at a lower level, within theget
andput
methods of theCubbyHole
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 theConsumer
and generates two numbers before theConsumer
has a chance to consume the first one. Thus theConsumer
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 theProducer
and consumes the same value twice. In this situation, theConsumer
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 theProducer
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 theProducer
be synchronized with the retrieval of an integer from theCubbyHole
by theConsumer
. TheConsumer
must consume each integer exactly once.The activities of the
Producer
andConsumer
must be synchronized in two ways. First, the two threads must not simultaneously access theCubbyHole
. 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 theConsumer
that the value is ready and theConsumer
must have some way to indicate that the value has been retrieved. TheThread
class provides a collection of methods--wait
,notify
, andnotifyAll
--to help threads wait for a condition and notify other threads of when that condition changes. Using thenotifyAll
andwait
Methods has more information.The Main Program
Here's a small stand-alone Java application that creates aCubbyHole
object, aProducer
, aConsumer
, and then starts both theProducer
and theConsumer
.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
Doing Two or More Tasks At Once: Threads |