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.
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