Deep analysis of Abstractqueuedsynchronizer exclusive lock implementation principle: Reentranlock__concurrent

Source: Internet
Author: User
Tags cas prev semaphore
First, Reentranlock

I believe we have all used Reentranlock,reentranlock is the concurrent package next to implement concurrency of the tool class (Reentrantreadwritelock, Semaphore, Countdownlatch, etc.), It is an exclusive lock like synchronized, and their two locks are compared as follows:
1. The Reentrantlock implements the lock interface, providing the same mutex and visibility as the synchronized, as well as providing a reentrant nature.
2. There are some functional limitations in synchronized: Cannot break a thread that is waiting to acquire a lock and waits indefinitely for a lock to be acquired. Reentrantlock more flexible, can provide better mobility and performance, can be broken thread
3. Built-in locks are released automatically, and Reentrantlock releases must be manually released at finally
4. The reentranlock efficiency will be much better than synchronized in the large concurrent volume.
5. Lock can be lock.lockinterruptibly (()), can timeout (Trylock (long time, Timeunit), non-blocking (Trylock ()) to acquire the lock
More about lock and Synchronized:java concurrent programming: Lock

One of the most basic functions of a concurrency tool is to acquire locks and release locks , so have you ever wondered how Reentranlock can be implemented concurrently. Since Reentranlock can be disconnected, it is not possible to use synchronized internally. In fact, Reentranlock is just a tool class, its internal implementation is through a abstractqueuedsynchronizer (short Aqs) to achieve, AQS is the entire concurrent package in the core of the place, Other concurrency tools are also implemented using AQS, so below we'll analyze how Aqs is implemented by Reentranlock. Second, Aqs

From the user's point of view,the functionality of Aqs can be divided into two categories: exclusive and shared , all of its subclasses, either implementing and using its proprietary APIs, or using shared-lock functionality, rather than using two sets of APIs, even its most famous subclass Reentrantreadwritelock, is also through two internal classes: Read lock and write lock, implemented two sets of APIs to achieve, why do this, and then we analyze, so far, we only need to understand that aqs in the function of exclusive control and shared control two functions can be
In the Aqs class, there is a member variable called State, and in Reentranlock he indicates the number of threads acquiring the lock, if state=0, indicates that there is no ready-made lock, 1 indicates that a lock has been acquired, and that greater than 1 indicates the number of reentrant Reentranlock's source code

First of all we have a general understanding of Reentranlock, Reentranlock is divided into fair and unfair locks, and Reentranlock is the embodiment of Aqs exclusive function
Fair Lock: The order in which each thread seizes the lock acquires the lock sequentially in the order in which the lock method is invoked, just like in the queue
Non-fair Lock: Indicates that the thread that gets the lock is in indefinite order, and who is lucky to acquire the lock
  
  
As you can see, two locks are inherited by a class called Sync, and each has two methods of lock and Tryacquire, let's look at the Sync class:
  
Originally, sync inherited from Aqs, and the two methods of fair and unfair locks Lock and Tryacquire are rewriting the sync method, which verifies the Reentrantlock principle is Aqs

Here, we've got a basic understanding, so let's think about how fair and unfair locks are implemented:
  There is a volatile-modified sign called key (in fact, the state in the Aqs above), which means that there is no thread to take the lock , and a thread-safe queue to maintain a bunch of suspended threads so that when the lock is returned, Can be notified to these suspended threads, can come to compete to acquire the lock.
Therefore, the only difference between the fair and the unfair lock is to acquire the lock, it is to get the lock first or enter the queue to wait for the Reentranlock lock

Let's see how the Reentranlock is locked: a fair lock .

When the


Fair lock calls Lock, will directly call the parent class Aqs acquire method, here passed in 1, very simple, is to tell a thread to acquire the lock, this is dead, so, on the contrary, in the release of the lock, is also passed in 1

in acquire, the first Call Tryacquire first to attempt to acquire the lock, and if not, call Addwaiter to create a waiter (current thread) to prevent the queue and then block itself, let's see how to try to acquire the lock. (Note: All two locks override the Aqs Tryacquire method)

        Protected Final Boolean tryacquire (int acquires) {//First get the current thread of the lock final thread present = T
            Hread.currentthread ();
            Gets the current state int c = GetState (); If there is currently no thread to get the lock if (c = = 0) {//hasqueuedpredecessors Indicates whether the current queue thread is waiting//indicates that no threads are waiting, Status if (!hasqueuedpredecessors () && compareandsetstate (0, Acquir) with CAS update state ES) {//And then set a property Exclusiveownerthread = Current, the record lock is taken away by the present thread Setexclusiveownerthread
                    (current);
                return true;
            }//If C!= 0, indicating that the thread has acquired the lock, and getexclusiveownerthread = = Current, which indicates that the lock is currently being acquired, is the current lock, so here is the reentrant.  else if (current = = Getexclusiveownerthread ()) {//re-entry, let the state be state+1, which means that one more time to enter int NEXTC =
                C + acquires; If the current state is <0, it indicates an exception if (NEXTC < 0) throw new ErRor ("Maximum lock count Exceeded");
                Set the current flag bit setstate (NEXTC);
            return true;
        //If the lock has been fetched and is not reentrant, returns false indicating that the lock failed to return false; }
    }

The logic to get the lock is clear, but here's what you need to know about CAS operations and the data structure of the queues, and here's what we're going to look at, back to Tryacquire.
  
If the lock succeeds, it does not, and if the lock fails, call Addwaiter and take the node.execlusive mode to put the current thread in the queue, mode is a field that represents the node type, which indicates whether the nodes are exclusive or shared

    Private node Addwaiter {
        //wraps the current thread in node.execlusive mode into 1 node node node
        = new Node ( Thread.CurrentThread (), mode);
        Use pred to represent the tail node
        pred = tail in the queue;
        If the tail node is not empty if
        (pred!= null) {
            Node.prev = pred;
            Inserting node into the tail of the list through CAS operations and pointing the tail node to node if there is a failure, there is concurrency, and the call to Enq
            if (Compareandsettail (pred, node)) {
                Pred.next = node ;
                return node;
            }
        }
        If the queue is empty, or the CAS fails, enter the Enq in the Dead Loop, "spin" to modify.
        Enq (node);
        return node;
    }

Looking at the memory structure of the Aqs Squadron, we know that the queue consists of nodes of node type, with at least two variables, one encapsulated thread, one encapsulated node type.
In fact, its memory structure is this (when the first node is inserted, the first node is an empty node, representing a thread that has acquired the lock, in fact, the first node of the queue is the node holding the lock):
  

    Private Node Enq (final node node) {
        //Enter Dead loop for
        (;;) {
            Node t = tail;
            If the tail node is null, the queue is null if
            (t = = null) {
                //This time a header node is added through CAs (that is, the yellow nodes on the top), and the tail also points to the head node, and then the next loop
                if ( Compareandsethead (New Node ())
                    tail = head;
            } else {//otherwise, insert Node of the current thread to the back of the tail node
                node.prev = t;
                if (Compareandsettail (t, node)) {
                    t.next = node;
                    and returns the previous node return T}} of the Insert node.
    

This completes the insertion of the thread node, and one more thing to do: suspend the current thread. , where the thread is suspended through the parkandcheckinterrupt within the acquirequeued

   Final Boolean acquirequeued (final node node, int arg) {Boolean failed = true;
            try {Boolean interrupted = false; for (;;)
                {final Node P = node.predecessor (); If the current node is the head description he is the first "valid" node in the queue, so try to get, if (p = = head && tryacquire (ARG)) {//after successful,
                    Remove the yellow node from the above image and Node1 into a head node.
                    Sethead (node); P.next = null;
                    Help GC failed = false;
                Returns true to have been inserted into the queue and is ready for a pending return interrupted; //Otherwise, check the status of the previous node to see if the thread that is currently getting the lock fails needs to be suspended. If necessary, the current thread is suspended by using the static method of the Locksopport class under the Juc package.
                Know to be awakened.
            if (Shouldparkafterfailedacquire (p, node) && parkandcheckinterrupt ()) interrupted = true; } finally {if (failed)//If there is an exception cancelacquire (node);/Cancel request, corresponding to the queue operation, the current section
        The point is removed from the queue. }
    }

There are a few things to note about this piece of code:

1. Node nodes, in addition to the current thread, node type, the queue before and after the elements of the variable, there is a variable called waitstatus, change the amount used to describe the state of the node, why the need for this state.

The reason is: Aqs queue, when there is concurrency, will certainly access a number of nodes, each node [G4] Represents a thread's state, some threads may "can't wait" to acquire the lock, need to give up the competition, exit the queue, some threads waiting for some conditions to meet, Satisfied before resuming execution (the description here is much like a tool class under a J.U.C package, reentranklock condition, in fact, condition is also a subclass of Aqs) and so on, in short, each thread has the state of each thread, but always need a variable to describe it, this variable is called waits Tatus, it has four different states:
Node canceled
Node waiting to be triggered
Node wait condition
The node state needs to propagate backwards.
  The current node cannot be suspended until the previous node of the current node is signal.

2. The thread's suspend and wake operations are implemented by invoking the JNI method using the unsafe class. Of course, it also provides an API to wake up after a specified time, which we'll talk about later.
  
(This piece of analysis comes from: http://www.infoq.com/cn/articles/jdk1.8-abstractqueuedsynchronizer#anch140431)

Let's take a thought:
1. Call the lock method to get the lock, while the lock method inner value calls the Aqs acquire (1)
2. Then try to acquire the lock, if the current state flag ==0, indicating that no thread has acquired the lock, and then determine if there is a queue waiting to get the lock, if there is no queue, the current thread is the first to get the lock, and then modify the flag bit, and use a variable exclusiveownerthread to record the current thread acquires the lock
3. If it is a reentrant state, also modify the state+1
4. If the lock has been taken, get failed
5. If the fetch fails, the current thread is packaged into a node and inserted into the queue.
6. Otherwise, check the status of the previous node to see if the thread that is currently getting the lock fails needs to be suspended. If necessary, the current thread is suspended by using the static method of the Locksopport class under the Juc package. Know to be awakened. Non-fair lock


As you can see here, the unfair lock, first of all, is to get the lock directly, if there is a concurrent acquisition failure, call Aqs acquire (1), and then acquire call the tryacquire of the unjust lock, and then call Nonfairtryacquire

 final boolean nonfairtryacquire (int acquires) {final Thread current = Thread.cur
            Rentthread ();
            int c = GetState (); If you are not currently acquiring a lock, get the lock directly, and then set a property Exclusiveownerthread = Current, the record lock is taken away by the present thread, here and all the nuances of fairness,
                    Fairness is also to be judged hasqueuedpredecessors () if (c = = 0) {if (compareandsetstate (0, acquires)) {
                    Setexclusiveownerthread (current);
                return true; }//if it is reentrant else if (current = = Getexclusiveownerthread ()) {int NEXTC = c + Acquir
                Es
                if (NEXTC < 0)//overflow throw new Error ("Maximum lock count Exceeded");
                SetState (NEXTC);
            return true;
        ///If the current lock fetch fails, returns false return false; }

The rest is just like a fair lock, if you fail to get here, you will be plugged into the queue to block up the sum of the Fair lock and the unfair lock when acquiring the lock, it will be honest to take the AQS process to obtain the lock is the first to seize the lock, to achieve the purpose of not queuing, If preemption fails, you can only queue up five, Reentrantlock release locks

From the above we can know that when the lock is occupied, the thread that gets the lock is queued (FIFO), then we think about what to do when we release it.
1. First the status of the lock to change
2. The head node in the queue to acquire the lock

Let's take a look at the code validation:
Call Unlock () when the lock is released, and then call the Aqs release method in the method
  
  
In the release method, the Tryrelease method is called first, because the Sync class inherited from Aqs overrides the Tryrelease method, so the sync Tryrelease method is executed at this time

        Protected Final Boolean tryrelease (int releases) {
            //the releases passed in here is 1, consistent with the 1 passed in when the lock was acquired, update state
            int c = getState ()- Releases;
            If the thread that is currently occupying the lock is not the thread that is trying to release the lock, an illegal exception is thrown if
            (Thread.CurrentThread ()!= getexclusiveownerthread ())
                throw new Illegalmonitorstateexception ();
            Boolean free = false;
            If the release succeeds, the variable that gets the lock is null, but because it is a reentrant relationship, not each release lock C equals 0, until the lock is last released, the AQS is not required to record which thread is acquiring the lock
            if (c = = 0) {Free
                = true;
                Setexclusiveownerthread (null);
            }
            SetState (c);
            return free;
        }

The lock is released at this time, and the thread to the queue's head is then notified to acquire the lock
  
Looking for the order is from the end of the queue to start looking forward to the first waitstatus less than 0 nodes, find this and node, using the Locksopport class to wake it, this Waitstatu said before, do not remember to see the front.
   Vi. Summary

In the concurrent package, the concurrency tools are essentially aqs as the core, so Aqs is also the most important part of concurrent programming. We start from Reentrantlock, to discuss the implementation of the AQS principle, is not difficult, in fact, the Aqs in a state position + a FIFO queue, record the acquisition of locks, release, etc. , This state is not necessarily used to refer to the lock, reentrantlock it to indicate the number of times the thread has repeatedly acquired the lock, Semaphore use it to represent the remaining number of licenses, futuretask use it to indicate the status of the task (not yet started, running, completed, and cancelled). At the same time, many CAS operations are also seen in the Aqs. Aqs has two features: exclusive and shared functionality, while Reentranlock is the embodiment of AQS exclusive functions, while Countdownlatch is the embodiment of shared functionality

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.