AQS (AbstractQueuedSynchronizer) source code Guide: lock acquisition and release

Posted by simpli on Mon, 20 Jan 2020 16:27:23 +0100

What is AQS?

AQS is an abstract synchronization framework, which can be used to implement a state dependent synchronizer.

Provides a framework for implementing blocking locks and related synchronizers (semaphores, events, etc) that rely on first-in-first-out (FIFO) wait queues. This class is designed to be a useful basis for most kinds of synchronizers that rely on a single atomic int value to represent state.
Provides a framework for implementing blocking locks and related synchronizers (semaphores, events, etc.) that rely on first in, first out (FIFO) waiting queues. This class is designed as a useful basis for most types of synchronizers, which rely on a single atomic int value to represent the state.
This class supports either or both a default exclusive mode and a shared mode.
This class supports the default exclusive and shared modes.
Even though this class is based on an internal FIFO queue, it does not automatically enforce FIFO acquisition policies. The core of exclusive synchronization takes the form:

Even if this class is based on an internal FIFO queue, it will not automatically execute the FIFO collection policy. The core forms of exclusive synchronization are as follows:


Acquire:
     while (!tryAcquire(arg)) {
        enqueue thread if it is not already queued;
        possibly block current thread;
     }
​
 Release:
     if (tryRelease(arg))
        unblock the first queued thread;
​
(Sharing mode is similar, but may contain cascading signals.)


AQS structure

    /**
     * Thread currently holding exclusive lock
     */
    private transient Thread exclusiveOwnerThread;
​
    /**
     * The head node of the waiting queue, generally the thread holding the lock (volatile)
     */
    private transient volatile Node head;
​
    /**
     * Each time a new node comes in, it is added to the tail of the waiting queue to form a volatile list
     */
    private transient volatile Node tail;
​
    /**
     * Lock state, 0 means not occupied, specifically implemented by subclass (volatile)
     */
    private volatile int state;
​
    // Node waiting for queue
    static final class Node {
        // Identity node is currently in shared mode
        static final Node SHARED = new Node();
        // Identity node is currently in exclusive mode
        static final Node EXCLUSIVE = null;
        
        // Greater than or equal to 0 indicates that the thread of this node has cancelled the scramble for this lock and does not need to wake up
        /**
         * The values are arranged numerically to simplify use.
         * Non-negative values mean that a node doesn't need to
         * signal. So, most code doesn't need to check for particular
         * values, just for sign.
         *
         * The field is initialized to 0 for normal sync nodes, and
         * CONDITION for condition nodes.  It is modified using CAS
         * (or when possible, unconditional volatile writes).
         */
        volatile int waitStatus;
​
        // Previous node
        volatile Node prev;
​
        // Next node
        volatile Node next;
​
        // Waiting thread
        volatile Thread thread;
        
        ...
    }


CLH queue Craig, Landin, and Hagersten lock queue

CLH is a non blocking FIFO queue. That is to say, when a node is inserted or removed, it will not block under concurrent conditions, but will ensure the atomicity of node insertion and removal through spin lock and CAS.

Source guide: ReentrantLock fair lock

 public static void main(String[] args) {
    ReentrantLock lock = new ReentrantLock(true);

    lock.lock();

    lock.unlock();
  }

Let's take a look at lock and then unlock

Process and source code interpretation of obtaining lock

// ReentrantLock.FairSync
	final void lock() {
            acquire(1);
     }

// AQS
	public final void acquire(int arg) {
        // 1) Current thread attempts to acquire lock, if successful, end
        // 2) If the lock fails to be obtained, the current thread will join the waiting queue and monitor the status of the previous node repeatedly after joining
        //     If the previous node is the head node, - try to get the lock, succeed, and jump out of the loop
        //     Otherwise, adjust according to the waitStatus of the previous node, including suspending the current thread, or adjusting the node whose previous node is not cancelled
        // 3) Finally, the current thread initiates interrupt Thread.currentThread().interrupt() 
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

// ReentrantLock.FairSync
// The current thread attempted to acquire a lock
// Return true: 1. No thread is waiting for the lock; 2. Reenter the lock. The thread already holds the lock, which can be acquired directly
	protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                // 1) Check whether there is a wait behind the head to wait for the lock. If there is a current thread, give up the preemptive lock
                // 2) If there is no thread waiting, CAS attempts to modify the state, and if successful, obtains the lock
                // 3) The current thread set is the thread currently holding exclusive lock
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
        	// 4) If the state is not 0, it means it is reentrant. You only need to judge whether the current thread is the thread holding exclusive lock
        	// 5) If yes, it can be re entered, and the state is increased
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
        	//6) In other cases, failed to obtain lock
            return false;
        }

// AQS
// Construct a node and add CAS to the tail of the waiting queue
	private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        Node pred = tail;
        //1) If the waiting queue is not empty, CAS will add the current thread to the end of the waiting queue
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        // 2) If the waiting queue is empty, CAS creates a new initialization CLH queue and the current thread joins the end of the waiting queue
        enq(node);
        return node;
    }

// AQS
// If the lock acquisition fails, the current thread will join the waiting queue
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            // Note that this is a loop, and it will exit only when the lock is obtained or the exception is thrown
            // If the previous node is the head node, try to obtain the lock
            // Otherwise, if the current thread needs to be suspended, it waits for the release of the lock
            for (;;) {
                // 1) Check the previous node of the current node. If it is the head node, try to obtain the lock
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                // 2) If the previous node of the current node is not the head node, or failed to obtain the lock, then judge whether to suspend the current thread?
                // 3) park suspends the current thread if it needs to be suspended
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            // 4) If the lock acquisition fails and an exception is thrown, the current thread cancels the preemption
            if (failed)
                cancelAcquire(node);
        }
    }

// AQS
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        // 1) If the state of the previous node is - 1, the current node needs to be suspended
        if (ws == Node.SIGNAL)
            return true;
        // 2) If the state of the previous node is greater than 0, it means that the preemption is cancelled. Then find a node's state ahead of the loop < = 0, and set the previous node of the current node as the found node
        if (ws > 0) {
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } 
        // 3) If the previous node is < = 0 and not equal to - 1, then the node state is 0 (the initial state of joining the waiting queue), - 2, - 3,
        //		At this time, CAS needs to be used to set the waitStatus of the previous node to node.signal (that is - 1) 
        else {
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

// AQS
// Suspend the current thread and test whether the current thread is interrupted
	private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

Process and source code interpretation of releasing lock

// ReentrantLock
	final void unlock() {
          sync.release(1);
     }

// AQS
	public final boolean release(int arg) {
        // 1) Release the lock and try to change the value of state
        if (tryRelease(arg)) {
            Node h = head;
            // 2) If there are threads in the waiting queue, the node state of the header will be corrected, and the next thread of the header node that is not in the cancelled state will be awakened
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

// ReentrantLock
// Determine whether the current thread is the thread holding the exclusive lock 
// If so, modify the state value, including reentrant lock minus one
// If not, throw an exception
	protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

// AQS
    private void unparkSuccessor(Node node) {
        /*
         * If status is negative (i.e., possibly needing signal) try
         * to clear in anticipation of signalling.  It is OK if this
         * fails or if status is changed by waiting thread.
         */
        int ws = node.waitStatus;
        // If the current waitstatus of the head node is less than 0, CAS will change it to 0
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        /*
         * Thread to unpark is held in successor, which is normally
         * just the next node.  But if cancelled or apparently null,
         * traverse backwards from tail to find the actual
         * non-cancelled successor.
         */
        // Wake up the successor node, but it is possible that the successor node cancels the wait (waitStatus==1)
   		// Look from the end of the queue to the front of all nodes with waitstatus < = 0
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
             // Wake up thread
            LockSupport.unpark(s.thread);
    }

 

by Siwu, Fengqing https://my.oschina.net/langxSpirit

Topics: Programming less