Concurrent Programming | After talking about AQS, why isn't the interviewer calm?

Insert picture description here

Can you tell me what AQS is

AQS is the abbreviation of AbstractQueueSynchronizer, which is used to build the basic framework of locks and other synchronization components. It defines a global int type state variable, and completes resource competition queuing through the built-in FIFO (first in first out) queue work.

What design patterns are used in AQS?

Template design mode

How to modify and access the status of the synchronizer?

  • getState: Get the current synchronization state
  • setState: Set the current synchronization state
  • compareAndSetState: Use CAS to set the current state. This method can guarantee the atomicity of state setting.

What template methods does AQS provide?

Method name Method description
Acquire() The exclusive lock acquires the synchronization status. If the current thread successfully acquires the synchronization status, this method will return, otherwise, it will enter the synchronization queue and wait. This method will call the rewritten tryAccquire() method
acquireShared() Acquire a shared lock. If the current thread does not acquire a shared lock, it will enter the synchronization waiting queue. The difference between it and an exclusive lock is that the shared lock can be held by multiple threads at the same time
release Release the synchronization state and notify the synchronizer to wake up the thread contained in the first node in the waiting queue.
releaseShare Release synchronization

A total of 7 template methods are listed above, which can be classified into the following three categories: acquisition and release of exclusive locks, acquisition and release of shared locks, query and setting of synchronization status

Can you talk about the data structure of the synchronization queue in AQS?

Fortunately, I scanned the interview questions in advance, otherwise I would be stuck here. First, let’s take a picture, come to the town hall

Insert picture description here

  • The current thread fails to obtain the synchronization state, the synchronizer constructs the current thread machine waiting state and other information into a Node node and adds it to the queue and puts it at the end of the queue. The synchronizer resets the tail node
  • After joining the queue, the current thread will be blocked
  • The synchronization state is released and the synchronizer resets the first node, and the synchronizer wakes up the first node in the waiting queue and lets it acquire the synchronization state again
In the above process, careful students should find a problem. Will thread safety problems occur when setting the head and tail nodes? If so, what should we do, let’s see how they do it

How does the synchronizer set the tail node to ensure thread safety?

Let's analyze first, why does thread safety appear when setting the tail node?

When multiple threads acquire the synchronization state (exclusive lock) at the same time, only one thread will compete successfully, and then other threads will be placed at the end of the waiting queue. When multiple threads are simultaneously packed to the end of the queue , It is equivalent to competing for the resource at the end at the same time, at this time thread safety issues


In order to ensure the thread safety of setting the tail element of the queue, the synchronizer provides the compareAndSetTail (Node expecr, Node update) method, which passes in the tail node that the current thread thinks and the node that is currently set to be the tail node, only the setting is successful. Only then formally associate the current node with the previous tail node.
Insert picture description here

When how to set the first node of the synchronizer, is it necessary to use cas to ensure thread safety? The interviewer was laughing evilly

The first reaction at that time was of course, but after the brain was running fast, when I recalled the interview questions last night, it seemed that I had brushed this question, and then he said: Of course not, the interviewer’s smile gradually disappeared, immediately I ask, why not? I'm kind of answer
first end node synchronization settings when needed cas guarantee thread safety because the tail node set when there are multiple threads simultaneously competing set, but when the end node is set There will not be multiple threads competing to set up at the same time.

Because, when releasing the synchronization state and setting the head node, only the thread that has obtained the synchronization state can be set, and the synchronization state of the exclusive lock can be obtained. Of course, there is only one thread. Therefore, it is impossible for thread safety problems to occur here. There is no need to use CAS to ensure thread safety. Simply disconnect the previous first node and set the next node as the first node

Insert picture description here

Do you know the source code for the acquisition and release of exclusive synchronization state?

The monologue in my heart is that this is already in my mind
public final void acquire(int arg) {
    
    
        if(!tryAcquire(arg)&&acquireQueued(addWaiter(Node.EXCLUSIVE),arg))
        seleInterrupt();
        }
}

Haha, although there are only a few lines of code, it completes the entire process of acquiring the synchronization lock. Don't believe me, I'll talk to you

  • The current thread calls the tryAcquire method implemented by the custom synchronizer. This method ensures that the thread can obtain the synchronization state safely. If the synchronization state acquisition fails, the subsequent process is executed

  • Construct an exclusive synchronization node, insert the Node into the tail of the synchronization queue by calling the addWaiter method, and call the acquireQueued method to spin to obtain the synchronization lock status, if not, block the current thread.

private Node addWaiter(Node node) {
    
    
Node node = new Node(Thread.currentThread(),mode);
// 快速尝试在尾部添加
Node pred = tail;
if(pred!=null){
    
    
node.prev = pred;
if(compareAndSetTail(pred,node)) {
    
    
    pred.next = node;
    return node;
}
}
enq(node);
}

private Node enq(final Node node) {
    
    
for(;;){
    
    
    Node t = tail;
    if(t==null){
    
    
        if(compareAndSetHead(new Node())){
    
    
        tail = node;
        }
    }else{
    
    
    node.prev = t;
    if(compareAndSetTail(t,node)){
    
    
    t.next = node;
    return t;
    }
    }
}
}

The above two pieces of code will be a bit dizzy the first time I look at it, and so is Le Zai. At that time, combined with the process analyzed above, it feels much clearer. Let me draw a mind map directly.

Insert picture description here

Let's take a break and continue to analyze the acquireQueued method to see what is done in it?

final boolean acquireQueued(final Node node,int arg){
    
    
boolean failed = true;
try{
    
    
    boolean interrupted = false;
    for(;;){
    
    
        final Node p = node.predcessor();
        if(p == head && tryAcquire(arg)) {
    
    
            setHead(node);
            p.next = null;
            failed = false;
            return interrupted;
        }
        }
}
}

Analyzing the above code snippet, it can be concluded that acquireQueued internally uses spin to obtain the synchronization state, and only if the predecessor node of the current node is the head node, will it try to call tryAcquire to obtain the synchronization state, otherwise continue to spin. When the interviewer saw that I was here, he added another sentence:
**Why can only the predecessor node be the head node before trying to obtain the synchronization status? **I really want to say something, am I about to say this! ! (Tough temper)
Insert picture description here

Insert picture description here

Why can only the predecessor node be the head node before trying to obtain synchronization

When the thread with the synchronization state releases the synchronization state, its successor node will be awakened. In order to maintain the FIFO principle, after the subsequent node is awakened, it is necessary to check whether its predecessor node is the head node, so as to ensure the FIFO principle. I won’t talk about the release of the exclusive lock. Here is a flowchart to summarize what I said.
Insert picture description here

You have been talking about exclusive locks and shared locks. What is the difference between them?

Teacher, let me draw you a picture. Draw North and North and North North. . . I sang suddenly, so embarrassing
Insert picture description here

The interviewer replied: This is the conceptual difference between them. Can you talk about the difference between AQS in code implementation?
Judging from the interviewer's answer, he actually wants to test if you have an in-depth understanding of the source code of AQS, because I have seen it, so I continued to talk to him.

  • Difference 1: Shared locks can be owned by multiple threads at the same time, and the synchronization status is successfully obtained. Shared locks are judged by judging whether the return value is greater than 0, and exclusive locks are judged by true or false

  • Difference 2: The exclusive lock does not need to care about thread safety when releasing the synchronization state, because only one thread is releasing the synchronization state, but the shared lock is owned by multiple threads at the same time, so thread safety needs to be guaranteed when releasing the synchronization state. Generally achieved by CAS + spin

The interviewer missed the last sentence, when will I start the job? At this moment, my heart is like this
Insert picture description here

This is the end of the interview content shared today. See you in the next issue.

Insert picture description here

A search on WeChat [Le Zai open talk] Follow the handsome me, reply [Dry goods], there will be a lot of interview materials and architect must-read books waiting for you to choose, including java basics, java concurrency, microservices, middleware, etc. The information is waiting for you.
The more you read the book without thinking, you will feel that you know a lot; and the more you read and think, the more clearly you will see that you know very little. --Voltaire

Guess you like

Origin blog.csdn.net/weixin_34311210/article/details/108570273