CAS and AQS (queue creation and maintenance and queue exception handling) source code analysis and understanding

One: Understanding of CAS

CAS: compare and replace

Basic implementation: V-memory address A-old expected value, B-new value to be modified

Basic implementation process: When updating a variable, only when the old expected value A of the variable is the same as the value V of the memory address, V will be modified to the new value B to be modified

Graphical process

 The cornerstone unsafe class implemented by CAS:

 Unsafe: Mainly to keep the CAS atomic operation comparison and replacement actions consistent (officially not recommended and rarely used for operations)

Frequently Asked Questions ABA Questions

The general meaning is that the memory value V to be operated by CAS is changed from A to B and then changed to A, which leads to the successful execution of the subsequent execution that should not have been successfully executed. 

demo

The solution is to add a version number to the data, such as ES does.

Two: Understanding of AQS

AQS (Abstract Queue Synchronizer):

1- is the basic framework for building locks or other synchronization components

2- It is the abstract implementation of the common basic part of various locks or synchronization components 

3- Guarantee the management and maintenance of the synchronization queue

4- Guarantee the management of the synchronization status

5-Thread blocking and wake-up management

The basic design idea of ​​AQS

  1. First encapsulate the competing threads and the waiting state into Node objects
  2. Put these node objects into a synchronous queue. This synchronous queue is a FIFO and a two-way queue is implemented based on the CLH queue.

AQS uses an int state to represent the synchronization state, such as: whether there is a thread to acquire the lock reentry times, etc. The specific meaning is defined by the specific subclass itself

Three: AQS source code analysis

In AQS, there is a waiting queue with node as the object. The clh queue is probably the following structure.

Node is to encapsulate the threads that come to compete for the lock and their states into a node object

There are Head and Tail nodes in AQS

Head node points to the first node1

Tail node points to the last node4

Between nodes, prev points to the previous node

Between nodes and nodes is next pointing to the next node

The main components of a node node

Source code analysis of the construction process of the synchronization queue

Assume that the current thread 0 has been occupied until the lock

A-Start entrance

Because thread 0 acquires the lock thread 1 enters the addWaiter method

 This method first takes out the current thread 1 to generate a node1

Give the tail value of AQS to pred 

tail=null so the if condition is not true

Enter the enq method with node1

 inside a vicious circle

because tail=null

So into the first if statement

Create a new node0 and set the value of AQS Head to node0 and tail to point to node0

Judge tail again! =null

enter esle

current t = node0

while node.prev = t 

So the prev of node1 points to node0

Change tail from pointing to node0 to pointing to node1 through compareAndSetTail

t.next = node point the next of node0 to node1

exiting the loop

Put back to the previous method

After the addWaiter method is executed, enter the acquireQueued method

 interrupted is a status bit for judging whether it is interrupted

Take out the pre value of node1 to p, so the value of p is node0

node0 is the head trying to acquire the lock The current condition is that thread 0 occupies the lock

Assumption 1: Thread 0 completes the task to release the lock Thread 1 acquires the lock

Point the value of head from node0 to node1

The next of node0 is disconnected and points to node1

At this time, actually disconnect node0 from the queue and wait for the GC to recycle.

return interrupted 

Assumption 2: Thread 0 is still using the lock or the lock is not acquired by thread 1 after thread 0 releases the lock but is acquired by the following thread

Then enter the second if  

Enter shouldParkAfterFailedAcquire

Get the waitStatus value of node0 to 0

So go in the compareAnd... method and change ws to SIGNAL

Return to the infinite loop of the previous method and enter this method

Enter the current method again to get ws as SIGNAL, so enter the first method

Return true directly and return to the previous method to enter the parkAndCheckInterrupt method

The thread here directly parks thread 1 and waits for the thread to wake up

Analysis of the source code of the maintenance process during the operation of the synchronization queue

The current condition is that thread 0 acquires the lock until thread lock 0 completes the work and releases the lock 

go this way

 

The conditions are not generated as kull and node0 is SIGNAL, enter the unparkSuccessor method

 The value of SIGNAL is -1 so <0 Here the status of node0 is changed to 0

Go down and get node1 for s

Judging that the value of s is not NUll, enter unpark and wake up the previous thread 1 at this time

Queue exception source code analysis during synchronous queue operation

The maintenance method is in finall in the acquireQueued method

When a thread is interrupted or crashed

Processing source code of front node interrupt 

first place

 Enter cancelAcquire

private void cancelAcquire(Node node) {
       
        if (node == null)
            return;
        //这里把当前的线程从这个node里面剔除
        node.thread = null;

        //取到node之前的线程
        Node pred = node.prev;
        //这里是如果node的前置节点node1也被取消
        while (pred.waitStatus > 0)
        //从node节点依次往前去找 直到找到没有被取消的node 把当前的node的pre值指向没有被取消的
            node上

            node.prev = pred = pred.prev;

        
        Node predNext = pred.next;

       //把自己的状态标记为CANCELLED 
        node.waitStatus = Node.CANCELLED;

        //如果node是tail 把AQS的tail设置为node的前一个node1
        if (node == tail && compareAndSetTail(node, pred)) {
        //node的前置node1的next设置为null 到这里其实就是把这个node和队列断开了联系
            compareAndSetNext(pred, predNext, null);
        } else {
        

           
            int ws;
            
            if (pred != head &&
                ((ws = pred.waitStatus) == Node.SIGNAL ||
                 (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
                pred.thread != null) {
               //这里就是把node的下一个node2拿到
                Node next = node.next;
                if (next != null && next.waitStatus <= 0)
               //把node的前置node1的next指向node后置node2
                    compareAndSetNext(pred, predNext, next);
            } else {
                unparkSuccessor(node);
            }
            //这里进行断开自己的node  同样就把自己从队列里面进行剔除
            node.next = node; // help GC
        }
    }

second processing place

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;
        //这里是如果node的前置node1中断了
        if (ws > 0) {
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
        //进行循环往前推找到一个没有中断的线程 把node的next指向那个正常的node
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } 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);
        }
        return false;
    }

Handling source code of post node interrupt 

place of first treatment

In release---------->unparkSuccessor

Node s = node.next;
        //拿到node的后置node2
        if (s == null || s.waitStatus > 0) {
        
            s = null;
            //把tail的值给了T  从后面逐步往前找 边走 边把t的值一直刷新为t的pre值
            //如果刷新的这个t值的状态不为中断状态 则 s的值一直保持和t值一样
            //如果刷新的这个t值的状态为中断状态 则 s的值不保持和t值一样
            //直到跑到t为第一个node值 终止循环
            //此时s为当前node之后第一个没有中断状态的node 把node的next指向这个s
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }

Welcome to study and exchange, please point out the shortcomings, if you like it, please like + bookmark, thank you guys

Guess you like

Origin blog.csdn.net/m0_67601895/article/details/129983059