Analysis of Reentrantlock parsing, lock and Unlock method

Source: Internet
Author: User
Tags cas

Before introducing Reentrantlock, we first introduce the background knowledge, that is, the knowledge points to be used. These points of knowledge include: Comparing and exchanging CAS (Compare and Swaps), Reentrantlock class structures (their parent classes, inner classes, and so on ).

Statement:I mainly through a popular language to summarize the content, to help everyone better understanding, memory, easier to understand the explanation of the book. For some professional statements, we still need to read.1. CASAn example is described. For the following classes
public class Blog {private int count;public int GetCount () {return count;} public synchronized int Increcount () {return ++count;}}

Here are 10 threads that have to add 1 to the variable count. In this case we will control it concurrently. This is the Synchronized keyword or the lock method using the Reentrantlock object, such as the Increcount () method. CAS does not need to use these, it can guarantee the correctness of the final result of variable A.Actually, CAS is referring to Sun.misc.UnsafeA generic term for some of the methods in this class. For example, unsafe has compareandswapint in this class, Compareandswaplong and other methods.The process of CAS is: it contains 3 parameters CAs (o,v,e,n). o indicates the object to be updated. v indicates which variable in the updated object is indicated, E is the value to compare, and if v==e, N is assigned to V.The Atomicinteger class in the JDK is analyzed here.
public class Atomicinteger extends number implements Java.io.Serializable {private static final long Serialversionuid    = 6214790243416807050L; Private static final unsafe unsafe = unsafe.getunsafe (); Here is the initialization of an unsafe object.    Because CAs is a method in this class.    Private static final long valueoffset; static {try {/* A Java object can be considered as a memory, each field must be placed in a certain order in the memory, taking into account the alignment requirements, perhaps these fields are not continuous placement, in this way can accurately tell you a field (That is, the value field below) is relative to the byte offset of the object's starting memory address, because it is a relative offset, so it has little to do with a specific object, and is more relevant to the definition of class and the implementation details of the virtual machine's memory model. The popular point is that in CAs (o,v,e,n), O is the object that you want to update, and V is what I want to do with this offset to find the value object in this object.       */Valueoffset = Unsafe.objectfieldoffset (AtomicInteger.class.getDeclaredField ("value"));    } catch (Exception ex) {throw new Error (ex);}    }//volatile to ensure the visibility of the variable.    private volatile int value;    Constructive public Atomicinteger (int initialvalue) {value = InitialValue;    Public Atomicinteger () {} public final int get () {return value;      }  Public final int getandincrement () {//Why is an infinite loop, the following method describes the for (;;)            {int current = get ();//Get present value int next = 1;        if (Compareandset (current, next)) return to current; }} public final Boolean compareandset (int expect, int update) {/*this refers to the object, Valueoffset is the previously mentioned offset, the value corresponding to the offset is found, and Is the value of the values variable, and if except equals that value, change value to update. Perhaps this is a problem, if I two threads read at the same time, the value of expect, equal to value, so that two threads to determine when the value is equal to except, the update is assigned to value, resulting in the final result is incorrect.        In fact, this method is the native method, but also synchronous, one thread to operate, another thread needs to wait, although the value of the expect of two threads is the same, but when the second thread calls this method, the value of values has changed. This returns false, so the for is used for wireless looping in the Getandincrement () method described above.    */return Unsafe.compareandswapint (This, valueoffset, expect, update); }    }

2, the Reentrantlock class structure Reentrantlock implements the lock interface, and the lock interface mainly provides the following methods:
   void Lock ();   void Lockinterruptibly () throws interruptedexception;   Boolean trylock ();   Boolean Trylock (long time, Timeunit unit) throws interruptedexception;   void unlock ();   Condition newcondition ();

The Reentrantlock class has a static internal abstract class sync, which inherits the Abstractqueuedsynchronizer class, The Abstractqueuedsynchronizer class inherits from Abstractownablesynchronizer. There are also two static inner classes in the Reentrantlock class, namely Nonfairsync (non-fair lock) and Fairsync (Fair lock). There is a static internal class node in the Abstractqueuedsynchronizer abstract class, which is the node that holds the waiting thread.
3, Analysis Lock () method Reentrantlock class has a member variable for private final sync sync; Reentrantlock's Lock (), UnLock (), and so on, are all methods of sync. As follows:
public class Reentrantlock implements Lock, java.io.Serializable {    private static final long Serialversionuid = 737398 4872572414699L;    /** Synchronizer providing all implementation mechanics */    private final sync sync;       Public Reentrantlock () {        sync = new Nonfairsync ();    }        Public Reentrantlock (Boolean fair) {        sync = (FAIR)? New Fairsync (): New Nonfairsync ();//When constructing a fair or unfair lock    }        p ublic void Lock () {        sync.lock ();    }        public void Unlock () {        sync.release (1);    }}
The difference between a fair lock and an unfair lock is that if there are 10 threads, the 1th thread acquires the lock, and then 8 threads are already waiting for the release lock. If it is a non-fair lock, the 10th thread can try to get the lock directly, it is possible that the 1th thread releases the lock and then the 10th one gets it directly. The fair lock is that the 10th thread must wait for the previous thread to acquire the lock and release it before it can get the lock, FIFO meaning. This is analyzed with an unfair lock. When the lock () method is called, that is, the Sync.lock () method is executed, the following is the code:
       Final void Lock () {            if (compareandsetstate (0, 1))//try to acquire the lock first. The method below illustrates                Setexclusiveownerthread (Thread.CurrentThread ());//If the lock is acquired, the current thread is assigned to the Exclusiveownerthread variable.            Else                acquire (1);//If it fails, this method is called. Keep looking at this method.        }

       Stateoffset = Unsafe.objectfieldoffset    //(AbstractQueuedSynchronizer.class.getDeclaredField ("state")) ;    Protected Final Boolean compareandsetstate (int expect, int update) {        //This stateoffset corresponds to the state field in the parent class of sync, This field is used to identify if the thread has acquired the lock    //If any, state>0, otherwise state==0; so if expect==state==0, then get the lock, and set the    //state to 1        . Return Unsafe.compareandswapint (this, stateoffset, expect, update);    }

    Public final void acquire (int arg) {    //try to acquire the lock first, if it fails to acquire the lock, that is! Tryacquire (ARG) is true to execute    //the following method. Note &&, preceded by true, is executed only after. After a lock failure is acquired, the thread is added to the wait queue        if (!tryacquire (ARG) &&                  acquirequeued (Addwaiter (node.exclusive), Arg))            Selfinterrupt ();    }

        Protected Final Boolean tryacquire (int acquires) {            return Nonfairtryacquire (acquires);//We explain it in an unfair lock.        }

    Final Boolean nonfairtryacquire (int acquires) {        final Thread current = Thread.CurrentThread ();        int c = getState ();        if (c = = 0) {//status is 0, indicating that no thread acquires the lock. Try to get the lock            if (compareandsetstate (0, acquires)) {                setexclusiveownerthread (current);                return true;            }        }        If the current thread is a thread stored in the Exclusiveownerthread variable, the current thread has        //acquired the lock, here is the current thread to get the lock again, so state should continue +1.        else if (current = = Getexclusiveownerthread ()) {            int nextc = c + acquires;            if (NEXTC < 0)//overflow                throw new Error ("Maximum lock count Exceeded");            SetState (NEXTC);            return true;        }        Failed to get lock, return false.        return false;    }
The Tryacquire (Arg) method is spoken, followed by acquirequeued (Addwaiter (node.exclusive), Arg).
    Private node Addwaiter (node mode) {    ///When the front thread node, you can look at some variables and methods in the node class, it is relatively simple.        node node = new node (thread.currentthread (), mode);                Node pred = tail;        Determine if there are no tail nodes (that is, there are waiting threads ahead). If there is a tail node, the node of the current thread is inserted into the tail of the queue        //That is, the current thread becomes the tail node.        if (pred! = null) {            Node.prev = pred;            The operation of CAs.            if (Compareandsettail (pred, node)) {                pred.next = node;                return node;            }        }        If there is no tail node, there is no waiting thread in front of it. Call the following method to create the wait queue        Enq (node);        return node;    }

    Private node Enq (final node node) {    //enq method is mainly to create headers, tail nodes, that is, wait for the queue, the following will use the CAS synchronous operation, plus    //for loop, prevent two threads simultaneously create the head and tail nodes. The code is easier to read for        (;;) {            Node t = tail;            if (t = = null) {//Must initialize                node H = new node ();//Dummy header                h.next = Node;                Node.prev = h;                if (Compareandsethead (h)) {                    tail = node;                    return h;                }            }            else {                Node.prev = t;                if (Compareandsettail (t, node)) {                    t.next = node;                    return t;}}}    

The Addwaiter method is analyzed, followed by the Acquirequeued method in Acquirequeued (Addwaiter (node.exclusive), Arg).

    This method is to continuously acquire the lock until the lock is successfully acquired by the final Boolean acquirequeued (final node node, int arg) {try {boolean inte            rrupted = false; for (;;)                {//Gets the previous node of the current node, final nodes P = node.predecessor ();                P==head means if the previous node is the head node, try to get the lock, why?                Because the fair lock will also call this method, here to ensure fairness, only when the front thread node in the queue of the first//to obtain a lock. if (p = = head && tryacquire (ARG)) {//If the result obtains a lock, the current node becomes the head node.                    Because the lock is acquired, the current node is moved out of the waiting//queue and becomes the head node.                    Sethead (node); P.next = null;                Help GC return interrupted; }//The first method is whether to block the current thread if the acquisition fails (analysis is followed).                Prevents threads from always making a for loop. The method behind && is to block the current thread and determine if it is interrupted. Note here that if a thread is interrupted during//waiting for a lock, this will assign interrupted to true, but not return. This//also keeps a for loop, knowing that the thread has acquired a lock, so the lock () method does not immediately respond to interrupts and must wait for the thread//lock to respond to interrupts. The corresponding method for immediately responding to interrupts is the lockinterruptibly () method if (SHOULDPArkafterfailedacquire (p, node) && parkandcheckinterrupt ()) interrupted = Tru            E            }} catch (RuntimeException ex) {Cancelacquire (node);        Throw ex; }    }

    private static Boolean Shouldparkafterfailedacquire (node pred, node node) {        //This pre is the previous node of the current node. Waitstatus has 4 states, this method is followed by a description of    int s = pred.waitstatus;    Less than 0, which is the signal state, or condition status        if (S < 0)            return true;      Greater than 0, which is the cancle state.        if (S > 0) {do    {Node.prev = pred = pred.prev;/    } while (Pred.waitstatus > 0);    pred.next = node;    }        else        //Why do you set the waitstatus of the parent node of the current node to 1, because        //in the Unlock () method, the next node is determined based on the waitstatus of the current node.        //If the current waitstatus==-1, it is indicated that there are subsequent nodes to set it. The subsequent node//is in a        blocking state. The unlock () method is analyzed later.            compareandsetwaitstatus (pred, 0, node.signal);        return false;    }

        static final int CANCELLED =  1;//This state indicates that the node has been canceled.                static final int SIGNAL    = -1;//This state indicates that the node subsequently has a blocked node                static final int CONDITION = -2;//This state is related to CONDITION, That is, the communication between threads through the condition method. I'm going to write about condition in my next blog post.


   Private Final Boolean parkandcheckinterrupt () {    //block current thread        Locksupport.park (this);        return thread.interrupted ();    }

4. UnLock () method
    public void Unlock () {        sync.release (1);    }
    Public final Boolean release (int arg) {    //tryrelease (ARG) is the state flag position of 0, which allows other threads to acquire locks.        if (Tryrelease (ARG)) {            Node h = head;            If the h.waitstatus is not equal to 0, the description behind the thread waits again, the front shouldparkafterfailedacquire ()            //method is described.            if (h! = NULL && H.waitstatus! = 0)            //This method is followed by an analysis. The main point is the waitstatus of the head node is set to 0, and the next node is notified.                unparksuccessor (h);            return true;        }        return false;    }

    private void Unparksuccessor (node node) {        /**     *) resets the Waitstatus state of the head node to 0     *        /Compareandsetwaitstatus ( node, node.signal, 0);        /**         * Gets the next node and, if the next node is canceled or empty, finds the first available thread * from the tail or the lookup         .         */        Node s = node.next;        if (s = = NULL | | s.waitstatus > 0) {            s = null;            for (node t = tail; t! = null && t! = Node; t = t.prev)                if (t.waitstatus <= 0)                    s = t;        }        if (s! = null)        //notifies the node.            Locksupport.unpark (S.thread);    }


5. Lock VS sychronized (quote from someone else's blog)

Abstractqueuedsynchronizer accommodates all blocking threads by constructing a blocking-based CLH queue, and operations on that queue are through Lock-free (CAS) operations, but for the thread that has acquired the lock, The Reentrantlock implements the function of biased locking.

The bottom of the synchronized is also a waiting queue based on CAS operations, but the JVM implements finer, dividing the waiting queue into contentionlist and entrylist, in order to reduce the thread's dequeue speed and, of course, to achieve a biased lock. There is no essential difference between the design and the data structure. However, synchronized also implements spin locks, which are optimized for different systems and hardware architectures, while lock relies entirely on system blocking to suspend waiting threads.

Of course, lock is more suitable for application layer extension than synchronized, can inherit Abstractqueuedsynchronizer define various implementations, such as implement read-write lock (Readwritelock), fair or unfair lock, and lock pair The condition should be more convenient and more flexible than wait/notify.


6. SummaryThe above main analysis from the lock to unlock the process, fair lock with this similar, read this, other methods such as lockinterruptibly (), it is easy to understand, if you do not understand can leave a message, or have any suggestions, I write the wrong place, can tell me, we grow together.


Analysis of Reentrantlock parsing, lock and Unlock method

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.