Most developers will probably never use AQS directly; the standard set of synchronizers covers a fairly wide range of situations. But seeing how the standard synchronizers are implemented can help clarify how they work.

The basic operations that an AQS-based synchronizer performs are some variants of acquire and release. Acquisition is the state-dependent operation and can always block. With a lock or semaphore, the meaning of acquire is straightforwardacquire the lock or a permitand the caller may have to wait until the synchronizer is in a state where that can happen. With CountDownLatch, acquire means "wait until the latch has reached its terminal state", and with FutureTask, it means "wait until the task has completed". Release is not a blocking operation; a release may allow threads blocked in acquire to proceed.

For a class to be state-dependent, it must have some state. AQS takes on the task of managing some of the state for the synchronizer class: it manages a single integer of state information that can be manipulated through the protected getState, setState, and compareAndSetState methods. This can be used to represent arbitrary state; for example, ReentrantLock uses it to represent the count of times the owning thread has acquired the lock, Semaphore uses it to represent the number of permits remaining, and FutureTask uses it to represent the state of the task (not yet started, running, completed, cancelled). Synchronizers can also manage additional state variables themselves; for example, ReentrantLock keeps track of the current lock owner so it can distinguish between reentrant and contended lock-acquisition requests.

Acquisition and release in AQS take the forms shown in Listing 14.13. Depending on the synchronizer, acquisition might be exclusive, as with Reentrant-Lock, or nonexclusive, as with Semaphore and CountDownLatch. An acquire operation has two parts. First, the synchronizer decides whether the current state permits acquisition; if so, the thread is allowed to proceed, and if not, the acquire blocks or fails. This decision is determined by the synchronizer semantics; for example, acquiring a lock can succeed if the lock is unheld, and acquiring a latch can succeed if the latch is in its terminal state.

The second part involves possibly updating the synchronizer state; one thread acquiring the synchronizer can affect whether other threads can acquire it. For example, acquiring a lock changes the lock state from "unheld" to "held", and acquiring a permit from a Semaphore reduces the number of permits left. On the other hand, the acquisition of a latch by one thread does not affect whether other threads can acquire it, so acquiring a latch does not change its state.

Listing 14.13. Canonical Forms for Acquisition and Release in AQS.

boolean acquire() throws InterruptedException {
 while (state does not permit acquire) {
 if (blocking acquisition requested) {
 enqueue current thread if not already queued
 block current thread
 return failure
 possibly update synchronization state
 dequeue thread if it was queued
 return success

void release() {
 update synchronization state
 if (new state may permit a blocked thread to acquire)
 unblock one or more queued threads

A synchronizer supporting exclusive acquisition should implement the protected methods TRyAcquire, TRyRelease, and isHeldExclusively, and those supporting shared acquisition should implement tryAcquireShared and TRyReleaseShared. The acquire, acquireShared, release, and releaseShared methods in AQS call the TRy forms of these methods in the synchronizer subclass to determine if the operation can proceed. The synchronizer subclass can use getState, setState, and compareAndSetState to examine and update the state according to its acquire and release semantics, and informs the base class through the return status whether the attempt to acquire or release the synchronizer was successful. For example, returning a negative value from TRyAcquireShared indicates acquisition failure; returning zero indicates the synchronizer was acquired exclusively; and returning a positive value indicates the synchronizer was acquired nonexclusively. The TRyRelease and TRyReleaseShared methods should return true if the release may have unblocked threads attempting to acquire the synchronizer.

To simplify implementation of locks that support condition queues (like ReentrantLock), AQS also provides machinery for constructing condition variables associated with synchronizers.

14.5.1. A Simple Latch

OneShotLatch in Listing 14.14 is a binary latch implemented using AQS. It has two public methods, await and signal, that correspond to acquisition and release. Initially, the latch is closed; any thread calling await blocks until the latch is opened. Once the latch is opened by a call to signal, waiting threads are released and threads that subsequently arrive at the latch will be allowed to proceed.

Listing 14.14. Binary Latch Using AbstractQueuedSynchronizer.

public class OneShotLatch {
 private final Sync sync = new Sync();

 public void signal() { sync.releaseShared(0); }

 public void await() throws InterruptedException {

 private class Sync extends AbstractQueuedSynchronizer {
 protected int tryAcquireShared(int ignored) {
 // Succeed if latch is open (state == 1), else fail
 return (getState() == 1) ? 1 : -1;

 protected boolean tryReleaseShared(int ignored) {
 setState(1); // Latch is now open
 return true; // Other threads may now be able to acquire


In OneShotLatch, the AQS state holds the latch stateclosed (zero) or open (one). The await method calls acquireSharedInterruptibly in AQS, which in turn consults the TRyAcquireShared method in OneShotLatch. The tryAcquire-Shared implementation must return a value indicating whether or not acquisition can proceed. If the latch has been previously opened, tryAcquireShared returns success, allowing the thread to pass; otherwise it returns a value indicating that the acquisition attempt failed. The acquireSharedInterruptibly method interprets failure to mean that the thread should be placed on the queue of waiting threads. Similarly, signal calls releaseShared, which causes tryReleaseShared to be consulted. The TRyReleaseShared implementation unconditionally sets the latch state to open and indicates (through its return value) that the synchronizer is in a fully released state. This causes AQS to let all waiting threads attempt to reacquire the synchronizer, and acquisition will now succeed because tryAcquireShared returns success.

OneShotLatch is a fully functional, usable, performant synchronizer, implemented in only twenty or so lines of code. Of course, it is missing some useful featuressuch as timed acquisition or the ability to inspect the latch statebut these are easy to implement as well, since AQS provides timed versions of the acquisition methods and utility methods for common inspection operations.

OneShotLatch could have been implemented by extending AQS rather than delegating to it, but this is undesirable for several reasons [EJ Item 14]. Doing so would undermine the simple (two-method) interface of OneShotLatch, and while the public methods of AQS won't allow callers to corrupt the latch state, callers could easily use them incorrectly. None of the synchronizers in java.util.concurrent extends AQS directlythey all delegate to private inner subclasses of AQS instead.

AQS in Java util concurrent Synchronizer Classes


Part I: Fundamentals

Thread Safety

Sharing Objects

Composing Objects

Building Blocks

Part II: Structuring Concurrent Applications

Task Execution

Cancellation and Shutdown

Applying Thread Pools

GUI Applications

Part III: Liveness, Performance, and Testing

Avoiding Liveness Hazards

Performance and Scalability

Testing Concurrent Programs

Part IV: Advanced Topics

Explicit Locks

Building Custom Synchronizers

Atomic Variables and Nonblocking Synchronization

The Java Memory Model

Java Concurrency in Practice
Java Concurrency in Practice
ISBN: 0321349601
EAN: 2147483647
Year: 2004
Pages: 141

Flylib.com © 2008-2020.
If you may any questions please contact us: flylib@qtcs.net