Synchronizers possess two kinds of methods : at least one
acquire operation that blocks the calling thread unless/until the synchronization state allows it to proceed, and at least one
release operation that changes synchronization state in a way that may allow one or more blocked threads to unblock.
java.util.concurrent package does not define a single unified API for synchronizers. Some are defined via common interfaces (e.g.,
Lock), but others contain only specialized versions. So,
release operations take a range of names and forms across different classes. For example, methods
FutureTask.get all map to
acquire operations in the framework. However, the package does maintain consistent conventions across classes to support a range of common usage options. When meaningful, each synchronizer supports:
+ Nonblocking synchronization attempts (for example,
tryLock) as well as blocking versions.
+ Optional timeouts, so applications can give up waiting.
+ Cancellability via interruption, usually separated into one version of acquire that is cancellable, and one that isn’t.
Synchronizers may vary according to whether they manage only
exclusive states – those in which only one thread at a time may continue past a possible blocking point – versus possible
shared states in which multiple threads can at least sometimes proceed.Regular lock classes of course maintain only exclusive state, but counting semaphores, for example, may be acquired by as many threads as the count permits. To be widely useful, the framework must support both modes of operation.
java.util.concurrent package also defines interface
Condition, supporting monitor-style
await/signal operations that may be associated with
exclusive Lock classes, and whose implementations are intrinsically intertwined with their associated Lock classes.
The basic ideas behind a synchronizer are quite straightforward. An acquire operation proceeds as:
1 2 3 4 5
And a release operation is:
1 2 3
Support for these operations requires the coordination of three basic components: + Atomically managing synchronization state + Blocking and unblocking threads + Maintaining queues
AbstractQueuedSynchronizer maintains synchronization state using only a single (32bit) int, and exports
compareAndSetState operations to access and update this state. These methods in turn rely on
java.util.concurrent.atomic support providing JSR133 (Java Memory Model) compliant
volatile semantics on
reads and writes, and access to native
loadlinked/store-conditional instructions to implement
compareAndSetState, that atomically sets state to a given new value only if it holds a given expected value.
Restricting synchronization state to a 32bit
int was a pragmatic decision. While JSR166 also provides atomic operations on 64bit
long fields, these must still be emulated using internal locks on enough platforms that the resulting synchronizers would not perform well. In the future, it seems likely that a second base class, specialized for use with 64bit state (i.e., with long control arguments), will be added. However, there is not now a compelling reason to include it in the package. Currently, 32 bits suffice for most applications. Only one
java.util.concurrent synchronizer class,
CyclicBarrier, would require more bits to maintain state, so instead uses locks (as do most higher-level utilities in the package).
Concrete classes based on
AbstractQueuedSynchronizer must define methods
tryRelease in terms of these exported state methods in order to control the
release operations. The
tryAcquire method must return
true if synchronization was acquired, and the
tryRelease method must return
true if the new synchronization state may allow future acquires. These methods accept a single
int argument that can be used to communicate desired state; for example in a reentrant lock, to re-establish the recursion count when re-acquiring the lock after returning from a condition wait.Many synchronizers do not need such an argument, and so just ignore it.
Until JSR166, there was no Java API available to block and unblock threads for purposes of creating synchronizers that are not based on built-in monitors. The only candidates were
Thread.resume, which are unusable because they encounter an unsolvable race problem: If an unblocking thread invokes
resume before the blocking thread has executed
resume operation will have no effect.
java.util.concurrent.locks package includes a
LockSupport class with methods that address this problem. Method
LockSupport.park blocks the current thread unless or until a
LockSupport.unpark has been issued. (Spurious wakeups are also permitted.) Calls to
unpark are not “counted”, so multiple unparks before a park only unblock a single park.Additionally, this applies per-thread, not per-synchronizer. A thread invoking park on a new synchronizer might return immediately because of a “leftover” unpark from a previous usage. However, in the absence of an unpark, its next invocation will block. While it would be possible to explicitly clear this state, it is not worth doing so. It is more efficient to invoke park multiple times when it happens to be necessary.This simple mechanism is similar to those used, at some level, in the Solaris-9 thread library, in WIN32 “consumable events”,and in the Linux NPTL thread library, and so maps efficiently to each of these on the most common platforms Java runs on.(However, the current Sun Hotspot JVM reference implementation on Solaris and Linux actually uses a pthread condvar in order to fit into the existing runtime design.) The park method also supports optional relative and absolute timeouts, and is integrated with JVM
Thread.interrupt support — interrupting a thread unparks it.
The heart of the framework is maintenance of queues of blocked threads, which are restricted here to FIFO queues. Thus, the framework does not support priority-based synchronization.These days, there is little controversy that the most appropriate choices for synchronization queues are non-blocking data structures that do not themselves need to be constructed using lower-level locks. And of these, there are two main candidates: variants of Mellor-Crummey and Scott (MCS) locks, and variants of Craig, Landin, and Hagersten (CLH) locks.Historically, CLH locks have been used only in spinlocks.However, they appeared more amenable than MCS for use in the synchronizer framework because they are more easily adapted to handle cancellation and timeouts, so were chosen as a basis. The resulting design is far enough removed from the original CLH structure to require explanation.A CLH queue is not very queue-like, because its enqueuing and dequeuing operations are intimately tied to its uses as a lock. It is a linked queue accessed via two atomically updatable fields,head and tail, both initially pointing to a dummy node.
A new node, node, is enqueued using an atomic operation:
release status for each node is kept in its predecessor node.So, the “spin” of a spinlock looks like:
A dequeue operation after this spin simply entails setting the head field to the node that just got the lock:
Omitting such details, the general form of the resulting implementation of the basic acquire operation (exclusive,noninterruptible, untimed case only) is:
1 2 3 4 5 6 7 8 9 10 11 12
And the release operation is:
1 2 3 4
The synchronizer framework provides a
ConditionObject class for use by synchronizers that maintain exclusive synchronization and conform to the Lock interface. Any number of condition objects may be attached to a lock object, providing classic monitor-style
signalAll operations, including those with timeouts, along with some inspection and monitoring methods.
ConditionObject class enables conditions to be efficiently integrated with other synchronization operations,again by fixing some design decisions. This class supports only Java-style monitor access rules in which condition operations are legal only when the lock owning the condition is held by the current thread. Thus, a
ConditionObject attached to a ReentrantLock acts in the same way as do built-in monitors (via
Object.wait etc), differing only in method names, extra functionality, and the fact that users can declare multiple conditions per lock.A
ConditionObject uses the same internal queue nodes as synchronizers, but maintains them on a separate condition queue.The signal operation is implemented as a queue transfer from the condition queue to the lock queue, without necessarily waking up the signalled thread before it has re-acquired its lock.
The basic await operation is:
1 2 3 4
And the signal operation is:
ConditionObject使用condition queue来维护等待队列，condition queue上的节点和lock queue中的节点相同，
signal是从condition queue中转移到lock queue中。
Because these operations are performed only when the lock is held, they can use sequential linked queue operations (using a
nextWaiter field in nodes) to maintain the condition queue.The transfer operation simply unlinks the first node from the condition queue, and then uses CLH insertion to attach it to the lock queue. The main complication in implementing these operations is dealing with cancellation of condition waits due to timeouts or
Thread.interrupt. A cancellation and signal occuring at approximately the same time encounter a race whose outcome conforms to the specifications for built-in monitors. As revised in JSR133, these require that if an interrupt occurs before a signal, then the
await method must, after re-acquiring the lock, throw
InterruptedException. But if it is interrupted after a signal, then the method must return without throwing an exception, but with its thread interrupt status set.
transfer操作是从将condition queue里的第一个节点从condition queue中移除，插入到lock queue中去。如果在
Here are sketches of how java.util.concurrent synchronizer classes are defined using this framework:
ReentrantLockclass uses synchronization state to hold the (recursive) lock count. When a lock is acquired, it also records the identity of the current thread to check recursions and detect illegal state exceptions when the wrong thread tries to unlock. The class also uses the provided
ConditionObject, and exports other monitoring and inspection methods. The class supports an optional “fair” mode by internally declaring two different
AbstractQueuedSynchronizersubclasses (the fair one disabling barging) and setting each
ReentrantLockinstance to use the appropriate one upon construction.
ReentrantReadWriteLockclass uses 16 bits of the synchronization state to hold the write lock count, and the remaining 16 bits to hold the read lock count. The
WriteLockis otherwise structured in the same way as
acquireSharedmethods to enable multiple readers.
Semaphoreclass (a counting semaphore) uses the synchronization state to hold the current count. It defines
acquireSharedto decrement the count or block if nonpositive, and
tryReleaseto increment the count, possibly unblocking threads if it is now positive.
CountDownLatchclass uses the synchronization state to represent the count. All acquires pass when it reaches zero.
FutureTaskclass uses the synchronization state to represent the run-state of a future (initial, running, cancelled,done). Setting or cancelling a future invokes
release,unblocking threads waiting for its computed value via
SynchronousQueueclass (a CSP-style handoff) uses internal wait-nodes that match up producers and consumers. It uses the synchronization state to allow a producer to proceed when a consumer takes the item, and vice-versa.
- 一行一行源码分析清楚 AbstractQueuedSynchronizer
- 一行一行源码分析清楚 AbstractQueuedSynchronizer（二）
- 一行一行源码分析清楚 AbstractQueuedSynchronizer（三）
- 深度解析 Java 8：JDK1.8 AbstractQueuedSynchronizer 的实现分析（上）
- 深度解析 Java 8：AbstractQueuedSynchronizer 的实现分析（下）
- AbstractQueuedSynchronizer 源码分析 (基于Java 8)