Java Reference
In-Depth Information
Recall from this example's overview that the Producer should execute first and every
value produced by the Producer should be consumed exactly once by the Consumer . How-
ever, when you study the first output of Fig. 23.13, notice that the Producer writes the
values 1 , 2 and 3 before the Consumer reads its first value ( 3 ). Therefore, the values 1 and 2
are lost . Later, the values 5 , 6 and 9 are lost , while 7 and 8 are read twice and 10 is read four
times. So the first output produces an incorrect total of 77, instead of the correct total of
55. In the second output, the Consumer reads the value -1 before the Producer ever writes
a value. The Consumer reads the value 1 five times before the Producer writes the value 2 .
Meanwhile, the values 5 , 7 , 8 , 9 and 10 are all lost —the last four because the Consumer ter-
minates before the Producer . An incorrect consumer total of 19 is displayed. (Lines in the
output where the Producer or Consumer has acted out of order are highlighted.)
Error-Prevention Tip 23.1
Access to a shared object by concurrent threads must be controlled carefully or a program
may produce incorrect results.
To solve the problems of lost and duplicated data, Section 23.6 presents an example in
which we use an ArrayBlockingQueue (from package java.util.concurrent ) to syn-
chronize access to the shared object, guaranteeing that each and every value will be pro-
cessed once and only once.
23.6 Producer/Consumer Relationship:
ArrayBlockingQueue
The best way to synchronize producer and consumer threads is to use classes from Java's
java.util.concurrent package that encapsulate the synchronization for you . Java includes
the class ArrayBlockingQueue —a fully implemented, thread-safe buffer class that imple-
ments interface BlockingQueue . This interface extends the Queue interface discussed in
Chapter 16 and declares methods put and take , the blocking equivalents of Queue meth-
ods offer and poll , respectively. Method put places an element at the end of the Block-
ingQueue , waiting if the queue is full. Method take removes an element from the head of
the BlockingQueue , waiting if the queue is empty. These methods make class Array-
BlockingQueue a good choice for implementing a shared buffer. Because method put
blocks until there's room in the buffer to write data, and method take blocks until there's
new data to read, the producer must produce a value first, the consumer correctly con-
sumes only after the producer writes a value and the producer correctly produces the next
value (after the first) only after the consumer reads the previous (or first) value. Array-
BlockingQueue stores the shared mutable data in an array, the size of which is specified as
an ArrayBlockingQueue constructor argument. Once created, an ArrayBlockingQueue is
fixed in size and will not expand to accommodate extra elements.
Class BlockingBuffer
Figures 23.14-23.15 demonstrate a Producer and a Consumer accessing an ArrayBlock-
ingQueue . Class BlockingBuffer (Fig. 23.14) uses an ArrayBlockingQueue object that
stores an Integer (line 7). Line 11 creates the ArrayBlockingQueue and passes 1 to the
constructor so that the object holds a single value to mimic the UnsynchronizedBuffer
example in Fig. 23.12. Lines 7 and 11 (Fig. 23.14) use generics, which we discussed in
 
 
Search WWH ::




Custom Search