Design pattern used by AQS - template method pattern

AQS adopts the standard template design mode and provides the following methods:

 // Exclusive Mode 
    public final void acquire(int arg);
    public final boolean release(int arg);
    // Exclusive interruptible
    public final void acquireInterruptibly(int arg)
            throws InterruptedException;
    // Exclusive with timeout
    public final boolean tryAcquireNanos(int arg, long nanosTimeout);

    // Sharing mode
    public final void acquireShared(int arg);
    public final boolean releaseShared(int arg);
    // Share interruptible
    public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException;
    // Shared tape timeout
    public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout) throws InterruptedException;

These methods have the final keyword, that is, rewriting is not allowed. Which methods can be rewritten?

//Exclusive Mode 
protected boolean tryAcquire(int arg);
protected boolean tryRelease(int arg);

//Sharing mode
protected int tryAcquireShared(int arg);
protected boolean tryReleaseShared(int arg);

//Exclusive mode
protected boolean isHeldExclusively();

What role do these template methods play in the code? Look at their callers. In the acquire method, when tryAcquire returns true, it means that the lock has been obtained. Otherwise, addWaiter enters the waiting queue first, and then acquirequeueueued waits for the lock to be obtained. Acquire interrupt is similar, except that the interrupt processing is different.

  public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    public final void acquireInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (!tryAcquire(arg))
            doAcquireInterruptibly(arg);
    }

Take acquire as an example. The real queue blocking wait lock is in addWaiter and acquirequeueueueueueueueued. What does the tryAcquire method need to do? Let's first look at the implementation of fair lock FairSync and non fair lock NonfairSync in ReentrantLock:

// Fair lock
    static final class FairSync extends Sync {
        // omit a lot
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }
    // Unfair lock
    static final class NonfairSync extends Sync {
        // omit a lot
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }
    abstract static class Sync extends AbstractQueuedSynchronizer {
        // omit a lot
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
}

The tryAcquire method of fair lock does roughly the following:

Check the status of the lock. If the lock is not occupied by any thread and there is no waiting thread, try to set the status with CAS. If successful, setExclusiveOwnerThread sets the thread occupying the lock as the current thread.
If the current thread already owns the lock, setState updates the number of reentries.
If the above two items fail, it means that the lock cannot be obtained immediately, and false is returned.

tryAcquire for non fair locks is roughly the same. It just lacks the ability to judge whether there are waiting threads, but tries its best to obtain the lock. What they have in common is to update the lock state and set the thread currently occupying the lock setExclusiveOwnerThread. The key is to update the lock state.

It is worth noting that why tryAcquire methods are not abstract methods, but provide a default exception throwing method? Because AQS contains many modes, and the actual user generally only needs one. If the implementation of default rejection is not provided, the user needs to manually override it, which is wordy.

  // in AQS
    protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }
    protected int tryAcquireShared(int arg) {
        throw new UnsupportedOperationException();
    }

Keywords: Concurrent Programming

Added by Anyaer on Mon, 03 Jan 2022 03:48:03 +0200