while (synchronization state does not allow acquire) { enqueue current thread if not already queued; possibly block current thread;
}
dequeue current thread if it was queued;
And a release operation is:
update synchronization state;
if (state may permit a blocked thread to acquire) unblock one or more queued threads;
Support for these operations requires the coordination of three basic components:
•Atomically managing synchronization state
•Blocking and unblocking threads
•Maintaining queues
It might be possible to create a framework that allows each of these three pieces to vary independently。 However, this would neither be very efficient nor usable。 For example, the information kept in queue nodes must mesh with that needed for unblocking, and the signatures of exported methods depend on the nature of synchronization state。
The central design decision in the synchronizer framework was to choose a concrete implementation of each of these three components, while still permitting a wide range of options in how they are used。 This intentionally limits the range of applicability, but provides efficient enough support that there is practically never a reason not to use the framework (and instead build synchronizers from scratch) in those cases where it does apply。
3。1Synchronization State
Class AbstractQueuedSynchronizer maintains synchro- nization state using only a single (32bit) int, and exports getState, setState, and 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 compare-and-swap or load- linked/store-conditional instructions to implement compare- AndSetState, 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 tryAcquire and tryRelease in terms of these exported state methods in order to control the acquire and 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。
3。2Blocking
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。suspend and Thread。resume, which are unusable because they encounter an unsolvable race problem: If an unblocking thread invokes resume before the blocking thread has executed suspend, the resume operation will have no effect。
The java。util。concurrent。locks package includes a LockSup- port 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。