We all know that AQS maintains a FIFO wait queue, and only when there is a resource competition will form a queue, so its process is exactly what? Let’s find out!

Let’s simulate a situation where t1 thread already holds the lock via CAS, state = 1, exclusiveOwnerThread = T1, and T2 wants to acquire the lock

The process of entrance

    private Node addWaiter(Node mode) {



        // Wrap the T2 thread as node

        Node node = new Node(Thread.currentThread(), mode);



        // Try to join a team. If you fail, enter enQ

        // The first time you join a queue, you must fail because the queue has not been initialized

        Node pred = tail;



        // If there is a tail node, the entry condition

        if(pred ! =null) {

            node.prev = pred;

            // If there is a tail element, set the current node to the tail node

            if (compareAndSetTail(pred, node)) {

                //pred <- node

                pred.next = node;

                return node;

            }

        }

        //t2 enter queue

        enq(node);

        return node;

    }



    private Node enq(final Node node) {

        // Loops twice, first to create the head node and second to append T2 to the end node

        for (;;) {

            Node t = tail;

            // Initialize the queue with a new Thread=null node as the header

            if (t == null) { 

                if (compareAndSetHead(new Node()))

                    tail = head;

            } else {

                //head <- t2

                node.prev = t;

                if (compareAndSetTail(t, node)) {

                    t.next = node;

                    return t;

                }

            }

        }

    }

Copy the code

After the first loop, a Thread = null header is created, and both head and tail point to the header


After the second loop, we append T2 to the end node and point the tail pointer to T2, making the end node and T2 bidirectionally linked


    final boolean acquireQueued(final Node node, int arg) {

        boolean failed = true;

        try {

            boolean interrupted = false;

            for (;;) {

                // Get the precursor node of T2

                final Node p = node.predecessor();

                // If it is the head node, try to acquire the lock because t1 may have already released the lock

                if (p == head && tryAcquire(arg)) {

                    setHead(node);

                    p.next = null// help GC

                    failed = false;

                    return interrupted;

                }

                // If T2 fails to acquire the lock, it needs to block

                // Attention!! When this method first enters, T2 will have the waitStatus CAS (0,-1) of the head node.

                // and return false to block T2 in the next loop

                if (shouldParkAfterFailedAcquire(p, node) &&

                    parkAndCheckInterrupt())

                    interrupted = true;

            }

        } finally {

            if (failed)

                cancelAcquire(node);

        }

    }

Copy the code

For the first time, the waitStatus of the first node is changed from 0 (default) -> -1 (signal), indicating that the subsequent nodes need to be woken up after the node exits the queue

The second loop calls locksuppurt.park (), which actually blocks T2

At this point, the entry of T2 has been completed, which can be summarized as follows:

  1. Wrap the T2 thread as a node node
  2. Create a new head node where Thread = null and append T2 to the head node to establish a reference relationship
  3. Change the header’s waitStatus to signal, blocking T2

The team process

   public final boolean release(int arg) {

           / / set the state = 0, setExclusiveOwnerThread (null)

        if (tryRelease(arg)) {

            Node h = head;

            // There is a head node with waitStatus equal to -1

            if(h ! =null&& h.waitStatus ! =0)

                // Wake up the successor node

                unparkSuccessor(h);

            return true;

        }

        return false;

    }



    private void unparkSuccessor(Node node) {



        int ws = node.waitStatus;

        if (ws < 0)

            // Set the header's waitStatus to 0 to prevent another thread from waking up

            compareAndSetWaitStatus(node, ws, 0);



        Node s = node.next;

        // Cancel the execution

        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;

        }

        / / wake t2

        if(s ! =null)

            LockSupport.unpark(s.thread);

    }

Copy the code

At this point, T2 is woken up and the acquireQueued method continues to execute, entering the loop

    final boolean acquireQueued(final Node node, int arg) {

        boolean failed = true;

        try {

            boolean interrupted = false;

            for (;;) {

                // Get the precursor node of T2

                final Node p = node.predecessor();

                // The attempt to acquire the lock succeeded because t1 has released the lock and the thread holding the lock has become T2

                if (p == head && tryAcquire(arg)) {

                    // Change the reference relationship by setting T2 as the head node

                    setHead(node);

                    p.next = null// help GC

                    failed = false;

                    return interrupted;

                }

.

        } finally {

            if (failed)

                cancelAcquire(node);

        }

    }

Copy the code



At this point, t1 queue is completed, to sum up:

  1. Change the waitStatus of T1 to 0 to avoid multiple threads waking up at the same time
  2. After waking up T2, the attempt to acquire the lock succeeded, i.eState = 1, exclusiveOwnerThread = t2
  3. Set T2 as the head node, head to T2, and Thread = null