Referring to the Java lock, we usually think of the synchronized keyword or Java Concurrent Util (hereafter referred to as JCU) package below the lock, today to grilled a grilled lock is how to achieve, For example, we can ask some questions first: what happens when we instantiate a reentrantlock and call its lock or unlock? What happens if multiple threads lock or unlcok the same lock instance at the same time?
AQS Reentrantlock Herding Effect
Summary
Referring to the Java lock, we usually think of the synchronized keyword or Java Concurrent Util (hereafter referred to as JCU) package below the lock, today to grilled a grilled lock is how to achieve, For example, we can ask some questions first: what happens when we instantiate a reentrantlock and call its lock or unlock? What happens if multiple threads lock or unlcok the same lock instance at the same time?
What is a reentrant lock?
Reentrantlock is a reentrant lock, what is a re-entry lock? a reentrant lock is the thread that currently holds the lock that can acquire the lock multiple times without waiting. how can a reentrant lock be implemented? This is to say from the parent of an internal class sync of Reentrantlock, the parent class of sync is Abstractqueuedsynchronizer (hereafter referred to as AQS).
What is Aqs?
Aqs is a JDK1.5 provided by a FIFO waiting queue implementation of a framework for the implementation of the Synchronizer, the importance of this framework, so to speak, the JCU package, almost all of the key components of the lock, multi-threaded concurrency and thread synchronizer are based on the framework of AQS. The core idea of Aqs is to modify the state of the current lock by using an attribute such as the volatile int, with the unsafe tool to manipulate its atomicity. when the value of state is 0, the identity change lock is not occupied by any thread.
Architecture of the Reentrantlock lock
The architecture of Reentrantloc is relatively simple, consisting mainly of a sync's internal abstract class and two implementation classes of the Sync abstract class. As mentioned above, sync inherits from Aqs, and their structure is as follows:
In addition to Aqs, I put Aqs's father Abstractownablesynchronizer (hereafter referred to as AOS) also drew in, can slightly mention, AOS mainly provides a exclusiveownerthread attribute, Used to associate the thread that currently owns the location. In addition, the two implementation classes of sync are Nonfairsync and Fairsync, which can be guessed by name, one is for achieving fair lock, and one is for implementing an unfair lock. So why does sync have to be designed as an inner class? We can see what protect methods Aqs mainly provide to modify state states, and we find that sync is designed to be a secure, externally inaccessible inner class. Reentrantlock all involved in the AQS access to the sync, in fact, the sync is designed to be internal class mainly for security considerations, which is also the author in Aqs comments emphasized.
Waiting queue for Aqs
As part of the core implementation of AQS, for example to describe what this queue looks like, we assume that there are currently three threads Thread1, Thread2, Thread3 at the same time to compete for locks, if the result is Thread1 acquired lock, Thread2 and Thread3 Enter the waiting queue, so they look like this:
The wait queue for Aqs is based on a doubly linked list, the head node is not associated with threads, and the next two nodes are associated with Thread2 and Thread3, and they will be concatenated on this queue in sequence. This time, if the back of the thread comes in again, it will be treated as a queue tail.
1) into the queue
Let's see what happens when these three threads are competing for a lock at the same time.
Code:
?
| 12345 |
publicfinal void acquire(int arg) { if(!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt();} |
Interpretation:
Three threads come in at the same time, they will first through the CAs to modify state status, if the modification succeeds, then the competition succeeds, so this time three threads only one CAs succeeds, the other two threads fail, that is, Tryacquire returns false.
Next, Addwaiter puts the node of the exclusive type associated with the current thread into the queue:
Code:
?
| 12345678910111213 |
privateNode addWaiter(Node mode) { Node node = newNode(Thread.currentThread(), mode); Node pred = tail; if(pred != null) { node.prev = pred; if(compareAndSetTail(pred, node)) { pred.next = node; return node; } } enq(node); returnnode;} |
Interpretation:
If the end-of-line node is not NULL, then the queued thread is waiting, then the queue is directly queued. For our example, the logic here should be to go enq, that is, the beginning of the tail is null, in fact, this time the entire queue is null.
Code:
?
| 123456789101112131415 |
private Node enq(finalNode node) { for(;;) { Node t = tail; if(t == null) { // Must initialize if(compareAndSetHead(newNode())) tail = head; } else{ node.prev = t; if(compareAndSetTail(t, node)) { t.next = node; returnt; } } }} |
Interpretation:
If Thread2 and Thread3 simultaneously enter the Enq, and the T==null, then the CAS operation to initialize the queue, this time only one thread can succeed, and then they continue to enter the loop, the second time into the Else code block, this time the CAS operation, Put yourself in the end of the team, so this time is only one thread success, we assume that Thread2 success, Haha, Thread2 happy return, Thread3 lost the next cycle, finally into the queue success, return to their own.
2) Concurrency issues
Based on the above two pieces of code, How do they implement not to lock, when there are multiple threads, or a lot of threads at the same time, how to ensure that they will eventually be able to obediently into the queue without concurrency problems? This is part of the code of the classic, multi-threaded competition, hot spots, a single point at the end of the queue, a number of threads through the "cas+ dead loop" This free-lock gold partner to modify the queue, each can guarantee only one success, if the next retry, if the failure is n a thread, Then each thread has a maximum of loop n times and can eventually succeed.
3) Suspend wait thread
The above is just the implementation part of the Addwaiter, so what happens when the node goes into the queue? It is necessary to see how acquirequeued is achieved, in order to ensure that the article neat, code I do not post, comrades to check, we still take the above example to see, Thread2 and Thread3 has been put into the queue, into the acquirequeued:
For Thread2, its prev points to head, so it will try to acquire the lock once again, if it fails, it will be the head of the waitstatus value of signal, the next cycle to try to acquire the lock, if it fails, And this time the Prev node waitstatus is already signal, then this time the thread will be suspended through Locksupport.
For Thread3, its prev point to Thread2, so look directly at the Thread2 corresponding node waitstatus is signal, if not set it to signal, and then give yourself once to see if they are not qualified to acquire the lock, If Thread2 is still in front, and its waitstatus is signal, it will hang itself.
If Thread1 hold the lock tightly, then Thread2 and Thread3 are now in a suspended state, and head, and the waitstatus of thread are signal, though they have tried to acquire the lock several times during the whole process, but have failed. , failure can not die cycle ah, so it was suspended. The current status is as follows:
Lock release-waiting for thread to evoke
Let's take a look at what happened when Thread1 finally finished the thing and called unlock ready to release the lock.
Code:
?
| 123456789 |
public finalbooleanrelease(intarg) { if(tryRelease(arg)) { Node h = head; if(h != null && h.waitStatus != 0) unparkSuccessor(h); returntrue; } returnfalse;} |
Interpretation:
First of all, Thread1 will modify the state status of Aqs, before it is 1, it becomes 0, note this time for the non-fair lock is a good insertion opportunity, for example, if the lock is a fair lock, this time to THREAD4, then this lock will be THREAD4 Rob ...
We continue to take the conventional route to analyze, when the Thread1 modified state, to determine whether the queue is null, and whether the team header waitstatus is 0, if the waitstatus is 0, the queue no waiting thread, as our example, Team head of the waitstatus for Signal=-1, so this time to notify the queue of waiting thread, can come to take the lock, this is unparksuccessor do things, unparksuccessor mainly do three things:
Set the waitstatus of the team header to 0.
By moving from the tail of the queue to the head of the queue, the last waitstatus<=0 node is found, that is, the node that has not been cancelled recently, the head of the team points to this node.
This node wakes up, in fact this time Thread1 already out of the queue.
Still remember where the thread hangs up, said above, in acquirequeued inside, I did not paste code, to see OH. Here we can probably understand why this queue of Aqs is called FIFO queue, so each wake only wake up the team head waiting for the thread, let the team head wait for the thread first out.
Herd effect
Here is the herd effect, when there are multiple threads to compete for the same lock, assuming that the lock is occupied by a thread, then if there are thousands of threads waiting for the lock, there is a way to wake up the thousands of threads at the same time to go to the competition lock, this time a herd effect, the massive competition will inevitably cause the increase of resources So after all, only one thread can compete successfully, and the other threads should go back and wait honestly. Aqs's FIFO waiting queue provides a way to solve the herd-effect problem in lock contention: to maintain a FIFO queue, each node only cares about the state of its previous node, and the thread wakes up only the queue head waiting thread. In fact, this idea has been applied to the practice of distributed lock, see: Zookeeper Distributed lock improvement implementation scheme.
Summarize
This article provides a rough introduction to reentrantlock and the implementation of the lock implementation of the basic framework Aqs, roughly by the example of a three thread competition lock, from the lock, unlock process What happened to this problem, In-depth understanding of the Aqs status-based identity and FIFO wait queue work principle, and finally extended the introduction of the herd effect problem, Bo Master Caishuxueqian, also please advise.
Grilled Reentrantlock and Aqs realization principle