|
|
Doing Two or More Tasks At Once: Threads |
TheProducergenerates an integer between 0 and 9 (inclusive), stores it in aCubbyHoleobject, and prints the generated number. To make the synchronization problem more interesting, theProducersleeps 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 theProducerput 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); } } }ProducerandConsumerin this example share data through a commonCubbyHoleobject. And you will note that neither theProducernor theConsumermakes any effort whatsoever to ensure that theConsumeris getting each value produced once and only once. The synchronization between these two threads actually occurs at a lower level, within thegetandputmethods of theCubbyHoleobject. 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
Produceris quicker than theConsumerand generates two numbers before theConsumerhas a chance to consume the first one. Thus theConsumerwould 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 . . .Consumeris quicker than theProducerand consumes the same value twice. In this situation, theConsumerwould 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 . . .Consumerto get each integer produced by theProducerexactly 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
CubbyHoleby theProducerbe synchronized with the retrieval of an integer from theCubbyHoleby theConsumer. TheConsumermust consume each integer exactly once.The activities of the
ProducerandConsumermust 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
Producermust have some way to indicate to theConsumerthat the value is ready and theConsumermust have some way to indicate that the value has been retrieved. TheThreadclass 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 thenotifyAllandwaitMethods has more information.The Main Program
Here's a small stand-alone Java application that creates aCubbyHoleobject, aProducer, aConsumer, and then starts both theProducerand 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 |