AQS for java Concurrent Programming learning

Keywords: Java less

principle

The full name is AbstractQueuedSynchronizer. When a thread goes to get resources, it will judge whether there is a lock according to the state value state. If there is a lock, it will be added to the linked list. The thread in the linked list will determine whether the resources have been released by spinning. If it is released, it will get resources.

AQS structure

  1. volatile Node head: blocked head node
  2. volatile Node tail: the tail node of blocking. The new blocking node is added to the end.
  3. volatile int state: lock state, 0 indicates unoccupied, greater than or equal to 1 indicates occupied
  4. Thread exclusiveOwnerThread: current thread occupying lock

Node: node information of bidirectional linked list

  1. Node EXCLUSIVE: exclusive mode
  2. Volatile thread: current thread
  3. volatile Node prev: front node
  4. volatile Node next: post node
  5. volatile int waitStatus: status field, - 1: waiting to wake up, greater than 0, cancelled

Source code analysis

Take unfair lock as an example.

Lock up

lock

Get lock

final void lock() {
    if (compareAndSetState(0, 1))//Set to 1 if status is 0
        setExclusiveOwnerThread(Thread.currentThread());//Set occupation lock as current thread
    else
        acquire(1);//Resource occupied
}

acquire

After the lock is occupied, try to acquire it again. If the lock cannot be acquired, enter the blocking queue.

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

tryAcquire:

The non fairtryacquire method of Sync is called.

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();//Get current thread
    int c = getState();//Get current status
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {//For the current thread, reentry
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

addWaiter

Encapsulate the thread as a node and join the queue

private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail;//Get tail node
    if (pred != null) {
        node.prev = pred;//The front node of the current node is the tail node obtained.
        if (compareAndSetTail(pred, node)) {//Set current node as tail node
            pred.next = node;//If cas operation is successful, set bidirectional linked list
            return node;
        }
    }
    enq(node);
    return node;
}

enq

Add to tail node by spinning

private Node enq(final Node node) {
    for (;;) {
        Node t = tail;//Get tail node
        if (t == null) { // If the tail node is empty, initialize the head node and the tail node with the same address.
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {//Join tail node
                t.next = node;
                return t;
            }
        }
    }
}

acquireQueued

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();//Get front node
            if (p == head && tryAcquire(arg)) {//If the front node is head, and the lock is acquired
                setHead(node);//Set the current node above the head
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            //If it's not the team leader, or if it hasn't robbed other threads
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())//If the team header is awake, suspend it with parkAndCheckInterrupt
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

shouldParkAfterFailedAcquire

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
        /*
         * This node has already set status asking a release
         * to signal it, so it can safely park.
         */
        return true;//Wake up, return true
    if (ws > 0) {
        /*
         * Predecessor was cancelled. Skip over predecessors and
         * indicate retry.
         */
        do {
            node.prev = pred = pred.prev;//If it is cancelled, the cancelled predecessor node replaces the predecessor node of the current node
        } while (pred.waitStatus > 0);
        pred.next = node;//Two way linked list
    } else {
        /*
         * waitStatus must be 0 or PROPAGATE.  Indicate that we
         * need a signal, but don't park yet.  Caller will need to
         * retry to make sure it cannot acquire before parking.
         */
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);//Pre node status set to wake up
    }
    return false;
}

awaken

unlock

public void unlock() {
    sync.release(1);
}

release

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);//Wake up head node
        return true;
    }
    return false;
}

tryRelease

protected final boolean tryRelease(int releases) {
    int c = getState() - releases;//Possible reentry
    if (Thread.currentThread() != getExclusiveOwnerThread())//Throw exception for non current thread
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {//cas is not used because only the current display is locked
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}
 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 (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);//If the current waitstatus of the header node is less than 0, change it to 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.
     */
    Node s = node.next;
    //Wake up the next node. If the first node is empty, traverse from the tail to get the node whose waitStatus is less than 0.
    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)
        LockSupport.unpark(s.thread);//awaken
}

Posted by billf on Thu, 17 Oct 2019 14:27:44 -0700