Java Reference
In-Depth Information
Lock
and
Condition
vs. the
synchronized
Keyword
In some applications, using
Lock
and
Condition
objects may be preferable to using the
syn-
chronized
keyword.
Lock
s allow you to
interrupt
waiting threads or to specify a
timeout
for
waiting to acquire a lock, which is not possible using the
synchronized
keyword. Also, a
Lock
is
not
constrained to be acquired and released in the
same
block of code, which is the
case with the
synchronized
keyword.
Condition
objects allow you to specify multiple con-
ditions on which threads may
wait
. Thus, it's possible to indicate to waiting threads that a
specific condition object is now true by calling
signal
or
signallAll
on that
Condition
ob-
ject. With
synchronized
, there's no way to explicitly state the condition on which threads
are waiting, and thus there's no way to notify threads waiting on one condition that they may
proceed without also signaling threads waiting on any other conditions. There are other pos-
sible advantages to using
Lock
and
Condition
objects, but generally it's best to use the
syn-
chronized
keyword unless your application requires advanced synchronization capabilities.
Software Engineering Observation 23.7
Think of
Lock
and
Condition
as an advanced version of
synchronized
.
Lock
and
Condition
support timed waits, interruptible waits and multiple
Condition
queues per
Lock
—if you do not need one of these features, you do not need
Lock
and
Condition
.
Error-Prevention Tip 23.6
Using interfaces
Lock
and
Condition
is error prone—
unlock
is not guaranteed to be
called, whereas the monitor in a
synchronized
statement will always be released when
the statement completes execution. Of course, you can guarantee that
unlock
will be
called if it's placed in a
finally
block, as we do in Fig. 23.20.
Using
Lock
s and
Condition
s to Implement Synchronization
We now implement the producer/consumer relationship using
Lock
and
Condition
ob-
jects to coordinate access to a shared single-element buffer (Figs. 23.20 and 23.21). In this
case, each produced value is correctly consumed exactly once. Again, we reuse interface
Buffer
and classes
Producer
and
Consumer
from the example in Section 23.5, except that
line 28 is removed from class
Producer
and class
Consumer
.
Class
SynchronizedBuffer
Class
SynchronizedBuffer
(Fig. 23.20) contains five fields. Line 11 creates a new object
of type
ReentrantLock
and assigns its reference to
Lock
variable
accessLock
. The
Reen-
trantLock
is created without the
fairness policy
because at any time only a single
Producer
or
Consumer
will be waiting to acquire the
Lock
in this example. Lines 14-15 create two
Condition
s using
Lock
method
newCondition
.
Condition
canWrite
contains a queue for
a
Producer
thread waiting while the buffer is
full
(i.e., there's data in the buffer that the
Consumer
has not read yet). If the buffer is
full
, the
Producer
calls method
await
on this
Condition
. When the
Consumer
reads data from a
full
buffer, it calls method
signal
on
this
Condition
.
Condition
canRead
contains a queue for a
Consumer
thread waiting while
the buffer is
empty
(i.e., there's no data in the buffer for the
Consumer
to read). If the buffer
is
empty
, the
Consumer
calls method
await
on this
Condition
. When the
Producer
writes
to the
empty
buffer, it calls method
signal
on this
Condition
. The
int
variable
buffer
(line 17) holds the shared mutable data. The
boolean
variable
occupied
(line 18) keeps
track of whether the buffer currently holds data (that the
Consumer
should read).