Java Reference
In-Depth Information
Consumer reads 6 Buffer cells occupied: 0
Producer writes 7 Buffer cells occupied: 1
Consumer reads 7 Buffer cells occupied: 0
Producer writes 8 Buffer cells occupied: 1
Consumer reads 8 Buffer cells occupied: 0
Producer writes 9 Buffer cells occupied: 1
Consumer reads 9 Buffer cells occupied: 0
Producer writes 10 Buffer cells occupied: 1
Producer done producing
Terminating Producer
Consumer reads 10 Buffer cells occupied: 0
Consumer read values totaling 55
Terminating Consumer
Fig. 23.15
|
Two threads manipulating a blocking buffer that properly implements the
producer/consumer relationship. (Part 2 of 2.)
While methods
put
and
take
of
ArrayBlockingQueue
are properly synchronized,
BlockingBuffer
methods
blockingPut
and
blockingGet
(Fig. 23.14) are not declared to
be synchronized. Thus, the statements performed in method
blockingPut
—the
put
oper-
ation (line 17) and the output (lines 18-19)—are
not atomic
; nor are the statements in
method
blockingGet
—the
take
operation (line 25) and the output (lines 26-27). So
there's no guarantee that each output will occur immediately after the corresponding
put
or
take
operation, and the outputs may appear out of order. Even if they do, the
Array-
BlockingQueue
object is properly synchronizing access to the data, as evidenced by the fact
that the sum of values read by the consumer is always correct.
synchronized
,
wait
,
notify
and
notifyAll
[
Note:
This section is intended for
advanced
programmers who want to control synchro-
nization.
2
] The previous example showed how multiple threads can share a single-element
buffer in a thread-safe manner by using the
ArrayBlockingQueue
class that encapsulates
the synchronization necessary to protect the shared mutable data. For educational purpos-
es, we now explain how you can implement a shared buffer yourself using the
synchro-
nized
keyword and methods of class
Object
.
Using an
ArrayBlockingQueue
generally
results in more-maintainable, better-performing code.
After identifying the shared mutable data and the
synchronization policy
(i.e., associ-
ating the data with a lock that guards it), the next step in synchronizing access to the buffer
is to implement methods
blockingGet
and
blockingPut
as
synchronized
methods. This
requires that a thread obtain the
monitor lock
on the
Buffer
object before attempting to
access the buffer data, but it does not automatically ensure that threads proceed with an
operation only if the buffer is in the proper state. We need a way to allow our threads to
wait
, depending on whether certain conditions are true. In the case of placing a new item
in the buffer, the condition that allows the operation to proceed is that the
buffer is not full
.
2.
For detailed information on
wait
,
notify
and
notifyAll
, see Chapter 14 of
Java Concurrency in
Practice
by Brian Goetz, et al., Addison-Wesley Professional, 2006.