preface

Text has been included to my lot warehouse, welcome Star:https://github.com/bin392328206/six-finger “the best time to plant a tree is ten years ago, the second is now” encourage everyone in the technology of writing a blog on the road

omg

This article, based on zi Lu teacher AQS, before this I AQS, most of its source code is vaguely understand the feeling, but read the zi Lu teacher’s article, like an Epiphany, ha ha, in fact, how to say? AQS this we must know, the basis of concurrent programming is AQS, as long as you have a little basis for concurrent programming, small 66 this document to ensure that you have a different understanding of concurrent programming, AQS.

Analysis of the

Built-in locking in Java has always been controversial. Before JDK 1.6, the performance of synchronized was always low. Although a large number of Lock optimization strategies were implemented after 1.6, synchronized still has some defects compared with Lock: Although synchronized provides a convenient implicit lock acquisition and lock release mechanism (based on JVM mechanism), it lacks the operability of lock acquisition and lock release. It can interrupt and timeout lock acquisition, and its performance is greatly reduced in high concurrency scenarios because of its exclusivity. Most companies currently have a JDK of 1.8, so I would say that sync and Lock are used in a variety of scenarios, and there is no good or bad.

How do I implement a synchronization myself

We know that AQS is a means of realizing resource competition and queuing, just like queuing up to enter the subway station and queue up to buy tickets. Then how do we realize this synchronous mechanism?

By spinning

volatile int status=0; Void lock(){while(! CompareAndSet (0,1)){} //lock} void unlock(){status=0; } Boolean compareAndSet(int except,int newValue){//casCopy the code

The pseudo code above is actually very simple, we can always go to spin a thread to lock, adopt the way of always spin + valatile + cas way can achieve resource security access, but we look at the pseudo code is actually a little faults, is the spin of the use of time will be very CPU resources, if threads competition very much, Well, the system is definitely broken, so what other ways can we improve it?

Disadvantages: CPU consumption. Threads that do not compete for the lock will always use CPU resources for CAS operations. If one thread has to spend Ns processing business logic after acquiring the lock, another thread will spend Ns CPU resources for nothing

Yield + spin synchronizes

volatile int status=0; void lock(){ while(! CompareAndSet (0, 1)) {yield (); } //lock

} void unlock(){ status=0; }

Copy the code

To solve the performance problem of a spinlock, a thread that has failed to compete for the lock must not idle, but rather yield CPU resources if it cannot acquire the lock. The yield() method yields CPU resources, and the yield method is invoked when the thread fails to compete for the lock. The spin +yield approach does not completely solve the problem, and yield is effective when the system has only two threads competing for the lock. It is important to note that this method only gives up the CPU now. It is possible that the operating system will choose to run this thread again. For example, there are 2000 threads in it.

Sleep + spin mode to achieve synchronization

volatile int status=0; void lock(){ while(! CompareAndSet (0, 1)) {sleep (10); } //lock

} void unlock(){ status=0; }

Copy the code

Cons: Why is sleep time 10? How do you control it? A lot of times even if you’re the caller you don’t really know what the time is

Park + spin to achieve synchronization

volatile int status=0; Queue parkQueue; // Collection array list

void lock(){ while(! CompareAndSet (0, 1)) {/ / park (); } //lock 10 minutes...... unlock() }

void unlock(){ lock_notify(); }

Void park(){// add the currentThread to the queue parkqueue.add (currentThread); // releaseCpu block releaseCpu(); } void lock_notify(){Thread t= parkqueue.header (); // Wake up the waiting thread unpark(t); }

Copy the code

Looking at the unsafe Queue, we used a Queue and a volatile status +cas+ pack(unsafe) to implement the synchronization Queue. In fact, the implementation principle of JUC lock is similar, but the complexity level is different

ReentrantLock source code analysis locking process

AQS (AbstractQueuedSynchronizer) the design of the main code (specific reference source)

private transient volatile Node head; Private TRANSIENT Node tail; private transient Node tail; // private volatile int state; // The lock status is 1 if the lock is successfully added, and 0 if reentrant +1 is unlockedCopy the code

Queue diagram in AQS


Design of the Node class

public class Node{ volatile Node prev; volatile Node next; volatile Thread thread; volatile int waitStatus; //Node For normal synchronous nodes, this field is initialized to 0, for conditional nodes, this field is initialized to * CONDITION. Modify it with CAS * (or unconditional volatile write if possible)}Copy the code

Focus on locking process

ReentrantLock (ReentrantLock, ReentrantLock); ReentrantLock (ReentrantLock, ReentrantLock); ReentrantLock (ReentrantLock, ReentrantLock); If the lock object is free, the state is 0. If the lock object is larger than zero, it is held by the thread. If the lock object is reentrant, the state is >1. Head: the head of a queue TS: the second thread that locks the lock TF: the first thread that locks the lock TC: the thread that currently locks the lock tl: Tn: the last thread to lock tn: any thread. Of course, these threads can be repeated, such as tf=tc=tl=tn when the lock is first held

First, a simple application

Follow along one by one, dubug recommends that you design at least 3 threads with dubug to see the difference between alternate and concurrent threads, the queue under each thread and the Node wrapped by each thread.

final ReentrantLock lock = new ReentrantLock(true); Thread t1= new Thread("t1"){ @Override public void run() { lock.lock(); logic(); lock.unlock(); }}; t1.start();Copy the code

Fair lock lock method source analysis

final void lock() { acquire(1); //1------ identifies the value changed after successful locking}Copy the code

Looc methods for unfair locks

final void lock() {
	if (compareAndSetState(0, 1))
		setExclusiveOwnerThread(Thread.currentThread());
	else
		 acquire(1);
} 
Copy the code

Logical graph of fairness and injustice


Fair lock lock is to have to judge whether they need to queue; Instead of a fair lock is to directly modify the CAS counter to see if the lock can be successfully added; If the lock fails, queue up (acquire); So fair or unfair; As soon as he’s in the AQS queue he’ll queue up; Once in line; Always stand in line and remember that

There was a very good article about this before

  • A picture to understand fair and unfair

Acquire method source code analysis

Then, in fact, the unfair lock, but more than a fair lock to acquire the lock, if it fails this time, then it can only say meek to queue, then the next need to call acquire method

Public final void acquire(int arg) {//tryAcquire(arg) acquireQueued //acquireQueued will not be called if the lock is successful //acquireQueued will not be called if the lock is successful //acquireQueued will not be called if the lock is successful //acquireQueued will not be called if the lock is successful. Interrupt next analysis // Why is this method needed? If (! tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }Copy the code

There are three core methods in the Acquire method, so let’s take a look at them

  • TryAcquire (ARG) attempts to acquire a lock. If the lock fails, the acquireQueued method will be called to join the queue. If the lock succeeds, it will not be called
  • AddWaiter adds to the queue of AQS
  • AcquireQueued what it needs to do after it is placed in the queue

Acquire first calls tryAcquire, notice that the result of tryAcquire is reversed

TryAcquire method source code analysis

Protected final Boolean tryAcquire(int acquires) {final Thread current = thread.currentThread (); Int c = getState(); int c = getState(); ----1, the lock is a free state // Hasqueued24, determine whether you need to queue it is a complicated method, and I will introduce it separately below. If there is no need to queue, try to establish a cas lock. If the lock is successful, the current thread is set as the thread that owns the lock and returns true if (! Hasqueuedtoraise () && compareAndSetState(0, acquires)) {// Set the current thread to the thread that owns the lock, SetExclusiveOwnerThread (current thread); return true; } // If C is not equal to 0 and the current thread is not equal to the thread that owns the lock, then the lock fails. Reentrantlock can be used for reentrantlock because it returns true if it is reentrantLock. Else if (current == getExclusiveOwnerThread()) {int nexTC = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }Copy the code

Sum up a few of their functions

  • Get the status of the current thread and AQS status. If the AQS status is 0, it means no one is using it. Another method is to determine whether I need to queue. If I do not need to queue, can the current thread directly cas to obtain the lock, right
  • If status is not equal to 0, and the thread occupied is not the same as the current thread, this is a repeatable lock. If it is the same, it is a repeatable lock. If it is not, return false to queue.

Hasqueuedestablishes the source code analysis to determine whether a queue is required

If tryacquire status is 0, do we say we need to determine whether we are afraid of queuing? Queuing is still a bit complicated. Let’s take a look at the big guy’s analysis

public final boolean hasQueuedPredecessors() { Node t = tail; Node h = head; Node s; /** * I don't have to wait in line. I can't think of a word to describe it. There are two cases * 1: the queue is not initialized, no queue is required, no queue is required, no queue is required; Lock directly, but may fail; Why did it fail? * Suppose two threads lock at the same time, both see that the queue is not initialized, both think that there is no need to queue, and both go to CAS change the counter; One of them will inevitably fail * for example, t1 will get the lock first, and then t2 will fail the CAS. In this case, T2 will initialize the queue and queue * * 2: The queue is initialized, but TC comes to lock, and realizes that it is the first queue in the queue. Like reentrant; * So what does it mean to be first in line? I'm going to explain it, and it's important to keep going; * He doesn't need to wait in line, doesn't need to wait in line, doesn't need to wait in line; Why not line up? * Because the first thread in the queue will attempt to acquire the lock, it is possible that the thread holding the lock has released the lock; * If the lock is released, the lock is executed directly. But if he is not released, he will line up, * so there is no line here, not really no line. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Return false to indicate that no queue is required, and the inverse (if (! Hasqueuedtoraise ()), so it would go directly to CAS to add a lock. * ---------- summary of the first case: the queue is not initialized and no one queue, so I directly do not queue, directly lock; Reasonable, well-founded and convincing; * For example, when you go to the train station to buy a ticket, the attendants are idle and the whole queue is not formed. * * If the queue is initialized, then the queue initialization process will be analyzed. =t; (Not absolutely, there is also the third case). = t returns true; But since it is &&, the code still needs to make subsequent judgments * (one might wonder, for example, that the queue is initialized; There's only one number in it, so how can it be the same at the beginning and end? * This is actually case 3 -- head equals tail; But let's say there's more than one data in the queue.) * if there's more than one data in the queue. Assign h. ext to s; S is the next Node in the queue. * In this case, S indicates that it is the first thread in the queue. * Why is it "s" and not "H"? Sure h is first in the queue, but not first in the queue; * Because h is the corresponding Node object or thread, it holds the lock, but does not participate in the queue; For example, if you are the first one to buy a ticket and the conductor is already serving you, you are not in line, only the person behind you is in line. * h in the queue is not participating in the queue. Refer to the queue initialization explanation below; * Because h is either a virtual node or a node that holds a lock; When is it virtual? When is the node that holds the lock? The following analysis * and determine whether s is equal to null, in fact, determine whether there is only one data in the queue; * if there are more than one queue, this is not true (s==null---->false), because h. ext is not null if there are more than one Node; * because it is | | operation if returns false, but also judge s.t hread! = Thread. CurrentThread (); There are two cases here. CurrentThread () returns true if the currentThread is not equal to the first queued Thread; * Then the overall result is h! T: = true; (s = = null false | | s.t hread! CurrentThread () = currentThread() {return true && true; * And the first person in line is not the same as the person who is competing now, so you just go to line up. CurrentThread () returns false to indicate that the currentThread competing for the lock is the same Thread as the first queued Thread. =t---->true; (s = = null false | | s.t hread! = currentThread() false-----> The true && false method is finally put back to false, so there is no need to queue * If there is no need to queue, call compareAndSetState(0, acquires) to change the counter and try locking; * There are two cases (sorry for this line of code; Zilu teacher teacher always said this AQS difficult, * you now carefully look at the meaning of this line of code, really not simple) * 2.2.1 the first case lock success? Some people will ask why it will be successful ah, such as this time H is holding the lock of that thread to execute * release the lock, so certainly successful ah; If yes, setExclusiveOwnerThread(current) is executed. Then return true to see the code * 2.2.2 The second case failed to lock? Some may ask why it failed. If this time h is holding the lock that thread did not execute * did not release the lock, then certainly failed ah; Else if (else if is relative to if (c == 0)) * So what if it fails? Back analysis; * *---------- Summary of the second case, if the queue is initialized, and there is at least one person in the queue, then queue yourself; But there was an episode; * ---------- he will check to see if he is the first person in line, and if he is, he will try to lock it. Try to see if the lock is released *---------- is also reasonable, like when you go to buy a ticket, if there is a queue, then you obediantly line up, but you will go to see if the first person in line is your girlfriend; *---------- if it's your girlfriend it's you (there are no real world examples of reentrant here, only boyfriends and girlfriends); * --------- you just ask your girlfriend to see if the conductor has finished and if it is your girlfriend's turn, because your girlfriend is the first one in line * question: for example, if you are in line, then he is in park state, if it is park state, how can you re-enter ah. * * * 3. The queue is initialized, but there is only one data in it; When does that happen? Is there only one data in ts when it locks? T =ts; t =ts; t =ts; t =ts; Tf for the node that holds the lock * why do this? Because AQS believes that H is never queued, assuming that you do not virtual node out, TS is H, * while TS actually needs to queue, because tf may not finish execution at this time and still has the lock, ts can not get the lock, so he needs to queue; Tf does not enter the queue when it locks. Tf does not enter the queue, so ts cannot be in the queue after TF. It can only create a Node whose thread is null. * Then the question arises; When exactly is there only one data in the queue? Let's say there are five people in the original queue, and the first four have finished * when the fifth thread gets the lock; He's going to set himself as the head, and the tail doesn't have one, so there's only one H in the queue and that's the fifth * and why is he going to set himself as the head? Well, it's already explained, because at this point the five threads are no longer queuing, he's got the lock; * So he does not participate in the queue, so he needs to be set to h; That is, the head; So within this time, there is only one node in the queue * about locking success to set itself as the head of the source, will be resolved to; Continuing with the code analysis of the third case * remember that at this point the queue is initialized, but there is only one data, and this data represents the thread that holds the lock * h! = t false = t false = t false There is no need to queue * * *------------- Summary of the third case: if there is only one node in the queue, and we have analyzed this case, *------------- this node is the node that currently holds the lock, so I do not need to queue, cas; Try locking *------------- This is the design principle of AQS, he will judge before you join the queue, there is no queue inside; *------------- there are two cases where people queue up; Queue uninitialized, do not need to line up * -- -- -- -- -- -- -- -- -- -- -- -- -- -- the queue is initialized, but only one node, is also no one line up, his first don't line up * -- -- -- -- -- -- -- -- -- -- -- -- -- -- as long as that it does not need to line up, then try locking; Queue after lock failure; * -- -- -- -- -- -- -- -- -- -- -- -- -- -- once again explained don't need to line up the ambiguity of the word * -- -- -- -- -- -- -- -- -- -- -- -- -- if lock fails, in go to the park, there are detailed below explain why such design source and * -- -- -- -- -- -- -- -- -- -- -- -- -- if the thread holding the lock to release the lock, ** */ return h! = && ((s = h.next) == null || s.thread ! = Thread.currentThread()); }Copy the code

It’s only two or three lines of code, but because it’s concurrent programming, it has a lot of meanings and situations, and that’s the beauty of concurrent programming, so let’s summarize

  • h! What does that mean? This is the first case, that is, when our queue is not initialized, we do not need to queue, like when you go to the train station to buy a ticket, all the attendants are idle, the whole queue is not formed; If no one is waiting in line, why would you wait in line and wait for the attendant to ask you if you need to buy a ticket?
  • Small 66 see also very meng force, this line of code to consider too much. So there’s a lot of things going on here, and you can take care of that, but when status is 0, it’s pretty cool to see if you need to queue

That’s it! TryAcquire (ARG) method, let me paste the code again for your convenience

Public final void acquire(int arg) {//tryAcquire(arg) acquireQueued //acquireQueued will not be called if the lock is successful. // The thread will park immediately after joining the queue, unpark after unlocking, and wake up to determine if it was interrupted. Next time explain if (! tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }Copy the code

AcquireQueued (addWaiter(Node.exclusive), ARG)) method resolution

Public final void acquire(int arg) {//tryAcquire(arg) acquireQueued //acquireQueued will not be called if the lock is successful //acquireQueued will not be called if the lock is successful //acquireQueued will not be called if the lock is successful //acquireQueued will not be called if the lock is successful. Interrupt next analysis // Why is this method needed? If (! tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }Copy the code

If the code can execute here and say tc needs to be queued needs to be queued there are two ways — in other words there are two ways the code can execute here: 1, TF held the lock, but did not release it, so tc needs to queue when it comes to lock, but this time – queue is not initialized. 2, TN (which does not matter which thread, but a thread) held the lock, so because lock tn! =tf(tf is in the first case, we will not consider tf now), so the queue must be initialized, TC to lock, then someone in the queue queue, so he also queue

So what does this say? The first step is to wrap a Node with the current thread and then put it inside the AQS and change the head tail of the AQS itself.

AddWaiter (Node.exclusive) source code analysis

Private Node addWaiter(Node mode) {// Since the element type in AQS queue is Node, Node = new Node(thread.currentThread (), mode); Node = new Node(thread.currentThread (), mode); Pred = tail; //tail = tail; / / determine whether Mr Pred is empty, is whether to end node, in fact, as long as the queue is initialized to tail affirmation is not empty, / / assumes the queue is only one element inside, so the tail and the capital is the element / / in other words if there is any queue initialization / / we said code execution to there are two kinds of circumstances, If the queue is initialized, the queue is initialized. If the queue is initialized, the queue is initialized. If the queue is initialized, the queue is initialized. The NC itself becomes the opposite end. = null) {// Set the last node of the current thread to pred, corresponding to the comment node.prev = pred; If (compareAndSetTail(pred, node)) {if (compareAndSetTail(pred, node)) {if (compareAndSetTail(pred, node)) The nc itself becomes the end corresponding to the comment pred.next = node on line 11; // Return node; }} // If the queue is not initialized, then the queue is not initialized. // Return nc return node; }

Private Node enq(final Node Node) {// This Node is the Node wrapped by the current thread. {// the queue is not initialized, so the first loop t==null Node t= tail; If (t == null) {// Must initialize //new Node is an instantiation of a Node object. //compareAndSetHead; Setting this nn as the head of the queue, CAS prevents multithreading and ensures atomic operations; If (compareAndSetHead(new Node()))) if (compareAndSetHead(new Node()))) // Then the first loop ends; And then I'm going to do the second loop, and the second loop I'm going to write down here, and then I'm going to go tail = head; } else { node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; }}}}

Private Node enq(final Node Node) {private Node enq(final Node Node) { {// copy the tail to t, for the second loop, so tail==nn, that is the new node node t = tail; If (t == null) {// Must initialize if (compareAndSetHead(new Node())) tail = head; } else {// not true, so enter else // first change the last node of the node represented by the current thread to nn, because this time nc needs to join the queue, the relationship needs to be maintained well // The maintenance relationship is to form a linked list, the last node of nc can only be NN, Node.prev = t; If (compareAndSetTail(t, node)) {if (compareAndSetTail(t, node)) {if (compareAndSetTail(t, node)) { Set the next node of nn to nc t. ext = node; // Then return t, that is, nn, the end of the loop, enq(node); Return t; return t; return t; }}}}

// This method has been explained to complete enq(node); // return nc, in either case return NC; Go to this addWaiter method to explain the completion of the return node;

Public class node {volatile node prev; volatile Node next; volatile Thread thread; }

-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- summary: The addWaiter method is to enqueue the NC - and maintain the list relationship of the queue. However, due to the complexity of the situation, different processing is done ------------------- mainly for whether the queue has been initialized. If not, a new Node nn is created as the header. The inside of the nn thread is null -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- acquireQueued next analysis method

Copy the code

To summarize this method, this method is actually to encapsulate the queue of AQS. The addWaiter method is to let the NC join the queue – and maintain the linked list relationship of the queue. However, due to the complexity of the situation, different processing is made for whether the queue has been initialized. AcquireQueued (); acquireQueued (); acquireQueued (); acquireQueued (); The reason is not to park.

AcquireQueued method source analysis

final boolean acquireQueued(final Node node, Int arg) {// The node is the node wrapped by the current thread. } // This is also a flag Boolean interrupted = false; // loop for (;;) {// Get the last node of nc, there are two cases; 1. The previous node is the head; Final Node p = node.predecessor(); // If the previous Node of nc is the head, then nc is the second element in the queue and the first Node in the queue. // There is no conflict between first and second; I explained above; // If nc is the second element in the queue, the first queued element will call tryAcquire to try locking. In other cases, it goes directly to park, because when the first queue is executed, it needs to check whether the thread holding the lock has released the lock, and then it will be my turn, so park will not be used. Why is tryAcquire going on here? // In fact, it is not, because the first tryAcquire determines whether there is a queue, if there is a queue, then I will join the queue; // When I joined the team, I found that the front man was the first one, holding the lock, so I did not give up, and asked the front man again if he finished // If he finished, I will not park, then he will do my own business; If he doesn't finish, THEN I go to park in the queue and wait for someone to call me // But if I go to the queue and find that the person in front of me is sleeping, the person in front of me is sleeping, If (p == head && tryAcquire(arg)) {// if (p == head && tryAcquire(arg)) {// if (p == head && tryAcquire(arg)) { // If you can enter the lock, you can enter the lock. So there's a low probability of that happening; But in a world of high concurrency this situation really needs to be considered // if the person in front of me is done and I get the lock, then the person in front of me goes straight out of the queue and I get the lead; This line of code sets itself as the head setHead(node); // The P here stands for the person who has just finished his work. How to get out of line? Delete the link from the list. // help GC // set the lock to false failed = false; // return false; Why return false? Return interrupted; } // The last node in the queue is not the head, which means THAT I am queuing, but my last node is not the first one in the queue. In either case, I need park at this time. Before park, I need to change the state of the last node to park. It is difficult to understand why I need to change the state of the last node to Park. Each node has a state, 0 by default, indicating stateless //-1 indicating in park; I couldn't change myself to -1, right? Why is that? Because you have to make sure you're park to change to -1; // If you want to change it to -1, you can change it to -1. But if you don't have park after the change, isn't that cheating? // You declare that you are single, but actually you are dating Liu Hongbin privately; This is a bit of a trap // So you have to park first; In a changing state; But the problem is that you park yourself; The CPU is completely freed, so there is no way to execute any code, so someone else has to change it; So you can see every time is after their own state of a node to himself - 1 / / to explain the method of shouldParkAfterFailedAcquire source blog continue next time if (shouldParkAfterFailedAcquire (p, Node) && // After the state of the last node is successfully changed; Park; ParkAndCheckInterrupt ()) interrupted = true; } } finally { if (failed) cancelAcquire(node); }}

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

Copy the code

Even though unsafe is the most important method, it calls the park method of the unsafe method to unload cpus

  • If it is the first found it in front of the node, and it’s going to try to get the lock, also got, like to buy a ticket, you know you only one person in the front, so will be back in a few minutes to ask you bought it, and then it just finished, it will say, you go to buy it, why to be like this, is, in the case of a front which it after buying the tickets, But it is just about to tell the next person, just when someone makes a phone call, it is called, if you don’t take the initiative to ask, then you have to wait for it to finish the phone. It is in the code that we unpack and change the status and when we change the status just happened to be split away from the CPU fragment, I went to the coincidence, so I found every line of code source code is really the essence, so awesome. That’s what this code is all about
If (p == head && tryAcquire(arg)) {if (p == head && tryAcquire(arg)) {if (p == head && tryAcquire(arg)) {if (p == head && tryAcquire(arg)) {if (p == head && tryAcquire(arg)) { I'm going to ask him if he's finished // if he can get into this one. So there's a low probability of that happening; But in a world of high concurrency this situation really needs to be considered // if the person in front of me is done and I get the lock, then the person in front of me goes straight out of the queue and I get the lead; This line of code sets itself as the head setHead(node); // The P here stands for the person who has just finished his work. How to get out of line? Delete the link from the list. // help GC // set the lock to false failed = false; // return false; Why return false? Return interrupted; }Copy the code

We take a look at shouldParkAfterFailedAcquire (Node, the Node)

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws = pred.waitStatus; If (ws == node. SIGNAL) return true; If (ws) > 0 {/ * * if the precursor to give up, then go straight to find, wait until recently found a normal state, side by side in the back of it. * Note: the abandoned nodes, because they are "pushed" in front of them, form a no-reference chain and will be removed by the security uncle later (GC collection)! */ do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else {// If the precursor works well, set the precursor status to SIGNAL and tell it to notify itself when it finishes retrieving the number. It could fail. He might have just been released! compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; }Copy the code

ReentrantLock Unlocking resolution

First call the template method sync.release(1)

Since fair locks are unlocked in the same way as non-fair locks, both reentrantLocks call sync.release(1).

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

Template method Release (1) analysis

Public final Boolean release(int arg) {// If (tryRelease(arg)) {Node h = head; // If the queue head is not empty and the subsequent thread is in some state, then the first queued thread is woken up. = null && h.waitStatus ! = 0) unparkSuccessor(h); return true; } return false; }Copy the code

TryRelease (ARG) attempts to unlock method analysis

Protected final Boolean tryRelease(int releases) {// Get the lock state-1 and then determine whether it is free (c=0?). Int c = getState() -releases the lock again; If (thread.currentThread ()! = getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; If (c == 0) {free = true; setExclusiveOwnerThread(null); } setState(c); return free; }Copy the code

Analysis of the method of waking up the queue waiting threads (H)

Private void unparksucceeded (Node Node) {// the state of the next Node thread ws int ws = node.waitStatus; If (ws < 0) compareAndSetWaitStatus(node, ws, 0); Node s = node.next; / / the queue is only one team head nodes or ws > 0 s thread needs to cancel from the synchronous queue waiting for the if (s = = null | | s. aitStatus > 0) {s = null; For (Node t = tail; for (Node t = tail; for (Node t = tail; t ! = null && t ! = node; t = t.prev) if (t.waitStatus <= 0) s = t; } // If this node is not null, wake it up. = null) LockSupport.unpark(s.thread); }Copy the code

At the end

After being interrupted by Thread. After being interrupted by a disturbance (), after being interrupted by a disturbance (), after being interrupted by a disturbance (), after being interrupted by a disturbance (), after being interrupted by a disturbance (), after being interrupted by a disturbance (), after being interrupted by a disturbance (), after being interrupted by a disturbance (), after being interrupted by a disturbance (), after being interrupted by a disturbance (), after being interrupted by a disturbance (), after being interrupted by a disturbance (), Must one-time all understand, anyway small 66 not so cattle force, I can only say that each study has a different harvest, learn a few times this way. AQS source code is really excellent. Behind will continue to learn the B station search AQS play the most is it, if the AQS and small 66 like a little knowledge can go to study.

Daily for praise

Well, that’s all for this article, everyone here is a real fan.

Creation is not easy, your support and recognition, is the biggest motivation for my creation, we will see in the next article

Six pulse excalibur | article “original” if there are any errors in this blog, please give criticisms, be obliged!

This article is formatted using MDNICE