A lock is a tool for controlling access to a shared resource by
multiple threads. Commonly, a lock provides exclusive access to a
shared resource: only one thread at a time can acquire the lock and
all access to the shared resource requires that the lock be
acquired first. However, some locks may allow concurrent access to
a shared resource, such as the read lock of a ReadWriteLock.
The use of synchronized methods or statements provides
access to the implicit monitor lock associated with every object, but
forces all lock acquisition and release to occur in a block-structured way:
when multiple locks are acquired they must be released in the opposite
order, and all locks must be released in the same lexical scope in which
they were acquired.
While the scoping mechanism for synchronized methods
and statements makes it much easier to program with monitor locks,
and helps avoid many common programming errors involving locks,
there are occasions where you need to work with locks in a more
flexible way. For example, some algorithms for traversing
concurrently accessed data structures require the use of
"hand-over-hand" or "chain locking": you
acquire the lock of node A, then node B, then release A and acquire
C, then release B and acquire D and so on. Implementations of the
Lock
interface enable the use of such techniques by
allowing a lock to be acquired and released in different scopes,
and allowing multiple locks to be acquired and released in any
order.
With this increased flexibility comes additional
responsibility. The absence of block-structured locking removes the
automatic release of locks that occurs with synchronized
methods and statements. In most cases, the following idiom
should be used:
Lock l = ...;
l.lock();
try {
// access the resource protected by this lock
} finally {
l.unlock();
}
A ScopedLock can be used to simplify the above to
Lock l = ...;
auto ScopedLock sl = new ScopedLock(l);
// access the resource protected by this lock
// the lock will be release at the end of the block
When locking and unlocking occur in different scopes, care must be
taken to ensure that all code that is executed while the lock is
held is protected by try-finally or try-catch to ensure that the
lock is released when necessary.
Lock
implementations provide additional functionality
over the use of synchronized methods and statements by
providing a non-blocking attempt to acquire a lock using tryLock(),
and an attempt to acquire
the lock that can timeout tryLock(long, TimeUnit).
A
Lock
class can also provide behavior and semantics
that is quite different from that of the implicit monitor lock,
such as guaranteed ordering, non-reentrant usage, or deadlock
detection. If an implementation provides such specialized semantics
then the implementation must document those semantics.
Note that
Lock
instances are just normal objects and can
themselves be used as the target in a synchronized statement.
Acquiring the
monitor lock of a
Lock
instance has no specified relationship
with invoking any of the lock methods of that instance.
It is recommended that to avoid confusion you never use
Lock
instances in this way, except within their own implementation.
\see ReentrantLock
\see Condition
\see ReadWriteLock