[TOC]

Refer to the link: www.bilibili.com/video/BV12K…

With CAS, we can implement optimistic lock operation, which causes threads to synchronize, but through the CAS source, we find that CAS can only modify a value in memory, but not to synchronize objects, so how to synchronize objects? At the same time, with multiple threads competing for a unified resource, how can you manage all the threads that need that resource? Thus, AQS was born.

Reference: In Java Virtual Machine

AbstractQueuedSynchronizer abstract synchronous queue AQS for short, it is the foundation of the realization of synchronizer components, and in the process of contract awarding the bottom of the lock is achieved by AQS. Structure is as follows

attribute

int state

In shared mode, you need to indicate the number of threads holding the shared lock.

Shared and exclusive locks (exclusive locks)

Shared lock: This lock can be held by multiple threads. The shared lock only supports read data. If a thread holds a shared lock on data, other data can only hold the shared lock on the data.

Exclusive lock: Only one thread can acquire the lock.

Shared and exclusive locks are different implementations of AQS

Node head & Node tail

A bidirectional linked list for maintaining a FIFO, with two nodes pointing to the head Node and the tail Node

Node

The structure of the nodes in the queue is shown in the figure above

Methods (take exclusive mode as an example)

tryAcquire(int arg)

protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
}
Copy the code

Attempts to obtain the lock. The system returns if obtaining the lock fails.

This method simply throws an exception that the AQS inheritance class needs to inherit to give the upper level open space for users to write business logic.

acquire(int arg)

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

After the tryAcquire method fails, it enters the wait queue

addWaiter(Node.EXCLUSIVE), arg)

It creates a Node and inserts the Node into the waiting queue.

private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
    	// Get the current tail node. Tail is an attribute of AQS
        Node pred = tail;
        if(pred ! =null) {
            node.prev = pred;
            // Try atomic operations to set the current node to the tail node
            // Tail can be modified by other threads after pred is obtained
            // compareAndSetTail(Node expect, Node update) reads the offset of tail
            // Determine whether the current pred is still the end of the queue (which may be modified by other threads), and if so, update the end of the queue to the current node
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                returnnode; }}// Call the full join method if the above attempt to join the team quickly fails
    	// For example, tail is modified
        enq(node);
        return node;
}
Copy the code

acquireQueued(final Node node, int arg)

After joining the queue, the lock is acquired by spin in the queue.

As can be seen from the code, the nodes after the head node form a wait queue

When the first node after the head gains the lock, the node becomes the new head node

final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            // Optional operation
            for (;;) {
                final Node p = node.predecessor();
                The current thread succeeded in obtaining the lock
                if (p == head && tryAcquire(arg)) {
                    // The current node serves as the head node
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                // Determine whether the thread needs to be suspended based on the current node state
                if (shouldParkAfterFailedAcquire(p, node) &&
                    	parkAndCheckInterrupt())
                    interrupted = true; }}finally {
            if(failed) cancelAcquire(node); }}Copy the code

boolean tryRelease(int arg)

Same as tryAcquire, as open to upper level methods

protected boolean tryRelease(int arg) {
        throw new UnsupportedOperationException();
}
Copy the code

boolean release(int arg)

The lock is released and the queue is notified to change the state of the thread in the waiting queue

public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if(h ! =null&& h.waitStatus ! =0)
            	// Pass in the head and wake up the node in the waiting queue
                unparkSuccessor(h);
            return true;
        }
        return false;
}
Copy the code

unparkSuccessor(Node node)

The head node notifies the nodes in the waiting queue when the resource is finished

In the following code, why doesn’t the wake up start at the beginning node

The search is not atomic, and searches from back to front may be because the queue is not built in order

  1. The back node pre points to the front node
  2. The former node next points to the latter node

From front to back, the search may be interrupted because step 2 has not been completed

private void unparkSuccessor(Node node) {
    	// Set the state of the head node
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);
		
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            // Start the search from the tail node, the first node after head and the node whose waitStatus <= 0
            for(Node t = tail; t ! =null&& t ! = node; t = t.prev)if (t.waitStatus <= 0)
                    s = t;
        }
    	// Wake up the found node, and then spin the acquire method to acquire the lock
        if(s ! =null)
            LockSupport.unpark(s.thread);
}
Copy the code

Sharing model

In shared mode, locks can be acquired by multiple threads and are represented by an increase in the state value.

After the thread completes the lock operation, it releases the lock and the state decreases.

Lock acquisition:

After the lock that uses the lock resource is released

  • Exclusive mode: Only the first node is awakened
  • Shared mode: Wakes up all nodes in the queue that are suspended in shared mode