(Turn) ReentrantLock the principle and source code analysis

Background: ReetrantLock AQS is based on the underlying implementation (CAS + CHL), there are two differences fair and unfair.

This underlying mechanism, it is necessary for analysis by tracking the source.

reference

ReentrantLock the principle and source code analysis

Source code analysis

Next we look at from the perspective of the source ReentrantLock realization of the principle of how it is to ensure reentrancy, but also how to achieve fair locks.

  ReentrantLock is based on the AQS, AQS is Java and the contract to build the foundation of the many synchronization component, which is a state by state variable of type int and a FIFO queue to retrieve a shared resource, such as waiting queue thread. AQS is a bottom framework, the Template Method, which defines a common more complex logic skeleton, such as queuing thread, blocking, wake-up, the extracted these complex but substantial portions common, these are the need to build Synchronization component users do not need care, the user only needs to rewrite some simple ways you can specify (in fact, for the state of shared variables to obtain the release of some simple operations).

  The above brief introduction at AQS, details refer to my other article " the Java and the cornerstone -AQS Detailed contract " will not repeat them here. First look at several commonly used method, we push from the top down.

No argument constructor (non-default lock fair)

of ReentrantLock public () { 
        Sync new new NonfairSync = (); // default non-fair 
    }

sync is a synchronous components inside ReentrantLock achieve, it is a static inner class of Reentrantlock, inherited from AQS, we'll analyze later.

  A constructor with a boolean value (whether fair)

of ReentrantLock public (Boolean Fair) { 
        Sync = Fair new new FairSync ():? new new NonfairSync (); // Fair to true, fair locked; on the contrary, unfair lock 
    }

See, here you can specify whether to adopt fair locks, FailSync and static inner classes NonFailSync also Reentrantlock, we have inherited from Sync .

summary

  In fact, from the above method to write this introduction, we can probably sort out the processing logic ReentrantLock, which defines three important internal static inner classes, Sync, NonFairSync, FairSync. As ReentrantLock Sync synchronization components in common, inherited the AQS (AQS to take advantage of top-level complex logic Well, thread queue, blocking, etc. wake-up); NonFairSync and FairSync then inherit Sync, call the common logical Sync, and then in their complete their specific internal logic (or fair fair).

 NonFairSync (non-reentrant lock fair)

Copy the code
static final class NonfairSync extends Sync {// inherited Sync 
        Private Long serialVersionUID = Final static 7316153563782823691L; 
        / ** acquire lock * / 
        Final void Lock () { 
            IF (compareAndSetState (0,. 1)) // set state CAS state, if the original value is 0, it is set to 1 
                setExclusiveOwnerThread (Thread.currentThread ()); // the current thread holds the lock has been marked as 
            the else 
                acquire (1); // If the setup fails, call acquire method of AQS, will acquire call tryAcquire we rewrite the following method. Here that the call fails, there are two cases: 1 current thread does not get to the resources, state 0, but the state from 0 to 1 when the other threads to seize the resources, the state revised, resulting in failure of the CAS; 2 state the original is not 0, that is, have the resources to get the thread, there may be other threads to obtain resources, there may be obtained by the current thread, the thread then repeated to acquire, so go tryAcquire in nonfairTryAcquire we should you can see the realization of the logic reentrant. 
        } 
        Protected Final Boolean to tryAcquire (int Acquires) {
            return nonfairTryAcquire (acquires); // call the method Sync  
        }
    }
Copy the code

 

nonfairTryAcquire()

Copy the code
Boolean nonfairTryAcquire Final (int Acquires) { 
            Final Thread.currentThread the Thread Current = (); // get the current thread 
            int c = getState (); // get the current state value 
            if (c == 0) {// If the state is 0 means that no thread to acquire resources, CAS will state is set to 1, and the current thread token I get to exclusive lock thread, returns to true 
                IF (compareAndSetState (0, acquires)) { 
                    setExclusiveOwnerThread (current); 
                    return to true; 
                } 
            } 
            the else IF (current getExclusiveOwnerThread == ()) {// if the state is not zero, but the current thread holding the lock thread 
                int nextc = c + acquires; // state accumulated. 1 
                IF (NEXTC <0) // int type overflow 
                    throw new Error ( "Maximum lock count exceeded");
                setState (nextc); // set state, state at this time is greater than 1, represents a multiple thread lock is eligible, that is the number of values for the state reentrant thread 
                return true; return true //, successfully acquiring the lock 
            } 
            return to false; // get the lock failed 
        }
Copy the code

 

Simple summarize the process: (ps: the process of acquiring the lock, the lock is shared implementation process)

    1. First, get state value is 0, which means that at this time there is no thread to acquire resources, CAS set it to 1, represents the set successfully acquired the exclusive lock;

    2. If the state is greater than 0, there must be thread has to seize resources, and this time again to determine whether preemption is their own, that is the case, the cumulative state, returns true, the successful re-entry, the value of state that is the number of threads reentrant ;

    3. other cases, the failure to acquire the lock.

  A look fair reentrant lock processing logic

  FairSync

Copy the code
Final class FairSync the extends Sync static { 
        Private Long serialVersionUID = Final static -3000897897090466540L; 

        Final void Lock () { 
            Acquire (. 1); // call the template method AQS directly acquire, acquire the following we will call this rewritten to tryAcquire 
        } 

        protected Final to tryAcquire Boolean (int acquires) { 
            Final Thread.currentThread the thread current = (); // get the current thread 
            int c = getState (); // Get state value 
            if (c == 0) {// If the state is 0, which means the current thread does not get to the resources, it can get direct resources yet? NO! This is not just unfair before the lock as the logical thing. See the following logic 
                if (! HasQueuedPredecessors () && // judge in chronological order, whether or not an application is locked in a row before their own thread, if there is no order to acquire, CAS set state, and mark the current thread holds an exclusive lock thread; otherwise, i.e., it can not get fair treatment!. 
                    compareAndSetState (0, acquires)) {
                    setExclusiveOwnerThread (Current); 
                    return to true; 
                } 
            } 
            the else IF (Current getExclusiveOwnerThread == ()) {// the processing logic re-entry, consistent with the above, will not be repeated 
                int NEXTC = C + Acquires; 
                IF (NEXTC <0) 
                    new new Error the throw ( "COUNT Lock the Maximum exceeded Number"); 
                the setState (NEXTC); 
                return to true; 
            } 
            return to false; 
        } 
    }
Copy the code

 You can see, fair and non-logical lock generally fair locks are the same, difference is that with! HasQueuedPredecessors () logic of this judgment, even if the state is zero, we can not hastily go directly to the acquisition, first to see there also thread in the queue, if not, to try to get done later processing. On the other hand, returns false, acquisition failure.

  Look at this to determine whether there is a logical thread in the queue

  hasQueuedPredecessors ()

Copy the code
Final Boolean hasQueuedPredecessors public () { 
        the Node T = tail; // End node 
        Node h = head; // head node 
        the Node S; 
        ! H = T return && 
            ((h.next = S) S == null || ! .thread = Thread.currentThread ()); // determine whether there ahead of its own thread 
    }
Copy the code

 It should be noted that this judgment whether there came in its own thread of logic before slightly around, we have to sort out next, by the code that, there are two cases will return true, we look at this decomposition logic (Note: returns true means that there are other threads lock application earlier than they need to give up to seize)

  1.  H! = T && (S = h.next) == null , this logic may be set up one head pointing to the head node, tail at this time is null. Consider this scenario: When some other thread to acquire the lock fails, the need to construct a join node synchronization queue (assuming that the sync queue is empty), when added, you need to create a meaningless puppet head node ( in the AQS enq method, which is a spin CAS operation), it is possible to point to this dummy head after completion of the node, the tail has not been directed to this node. Obviously, this is better than the current thread on the thread of time, therefore, returns true, indicating that there are waiting thread and come even earlier than their own.

  2. H! = T && (S = h.next)! = Null && s.thread! = Thread.currentThread () . There have been several synchronous queue queuing thread queue and the current thread is not the second child node, this situation will return true. If there is no s.thread! = Thread.currentThread () the judge, then what will happen? If the current thread already is the second child node (the first node and this was a meaningless puppet nodes) in synchronization queue, then thread holding the lock release resources, wake her second child node thread, the second node thread re tryAcquire (this logic acquireQueued method in the AQS), will call to hasQueuedPredecessors, without s.thread! = Thread.currentThread () to determine if this return value is true, the cause tryAcquire failure.

ps: check the current thread in front of a word is there waiting thread

  Finally, take a look at the ReentrantLock tryRelease, defined in Sync

Copy the code
Final Boolean tryRelease protected (int Releases) { 
            int getState C = () - Releases; // minus one resource 
            IF (Thread.currentThread () = getExclusiveOwnerThread ()!) 
                the throw new new IllegalMonitorStateException (); 
            Boolean = Free to false; 
            / / If the state value of 0 indicates that the current thread has been fully released clean, returns true, the upper AQS will realize that resources have been vacated. If not 0, then the thread still has resources, but it will release a re-entry of the resource, returns false. 
            IF (C == 0) { 
                Free = to true; // 
                setExclusiveOwnerThread (null); 
            } 
            the setState (C); 
            return Free; 
        }
Copy the code

 

to sum up

ReentrantLock a reentrant, fairness may be implemented mutex, it is designed based on the framework of AQS, reentrancy and fairness logic implemented not difficult to understand, each time re-entry, state is incremented by one, of course, when released, they have to release layer by layer. As for fairness, when attempting to acquire a lock one more judge: whether there are early in the synchronization of threads waiting in the queue, if so, to wait more than their application; if not, be allowed to seize.

 

ReentrantLock principle

ps: this blog talking about it more user-friendly

AQS using a FIFO queue represents a queue of threads waiting for the lock, queue head node called "sentinel node" or "dumb node", it is not associated with any thread. Other nodes associated with waiting threads, each node maintains a waiting state waitStatus

ReentrantLock basically can be summarized as: first by CAS attempt to acquire the lock. If at this time there is a thread already occupied the lock, then join the queue and AQS is suspended. When the lock is released, the team ranked first in the queue CLH thread will be awakened, then CAS try to acquire the lock again. At this time, if:

Unfair lock: If you come in at the same time there is another thread attempts to acquire, then it is possible to make this thread obtained ahead;

Fair lock: If you come in at the same time there is another thread attempts to acquire, when it is found that they are not the first words of the team, the team will be routed to the tail, the head of the queue to get a thread lock. (the difference)

Reentrant lock. Reentrant lock means the same thread can acquire the same lock multiple times. ReentrantLock are synchronized and reentrant lock.

Interruptible lock. Interruptible lock refers to the process thread attempts to acquire a lock in, can respond to interrupts. synchronized lock is not interrupted, and ReentrantLock provides the interrupt function.

Fair locking and non-locking fair. Fair lock means that multiple threads simultaneously try to acquire the same lock, order to obtain the lock in order to achieve a thread, rather than a fair lock thread is allowed to "jump the queue." fair and non-synchronized lock, and the default implementation of non-equity ReentrantLock lock, but can also be set to lock fair.

lock()

1. The first step. Attempt to acquire a lock. If you attempt to acquire a lock is successful, the method returns directly.

2. The second step, into the team. Since in the above-mentioned A thread lock already occupied, so the B and C perform tryAcquire failed, and the wait queue. If the thread holding the lock A dead link, then B and C will be suspended.

3. The third step is to hang. B and C are performed successively acquireQueued (final Node node, int arg). This approach has let the team into the thread tries to acquire the lock, if the failure will be suspended.

After the thread into the team can hang on the premise that the state of its predecessor node is SIGNAL, its meaning is "Hi, in front of the brothers, and if you get locked out of the team, remember to wake me!." So shouldParkAfterFailedAcquire will first determine whether the current node precursor state to meet the requirements, if they meet the return true, then call parkAndCheckInterrupt, will hang themselves. If not, look precursor node is> 0 (CANCELLED), if you traverse forward until the first to meet the requirements of the precursor, if not the precursor node status will be set to SIGNAL.

 Throughout the process, if the state is not the precursor node SIGNAL, then your own peace of mind can not be suspended, pending the need to find a reassuring point, at the same time you can try to see if there is no chance to try to compete lock.

    Finality queue may be as shown below

Copy the code
Final the Node class {static 
        / ** waitStatus value indicates that the thread has been canceled (or interrupted wait timeout) * / 
        static int CANCELED = Final. 1; 
        / ** waitStatus value indicates that subsequent thread needs to be awakened (unpaking) * / 
        static int = the SIGNAL -1 Final; 
        / ** waitStatus value indicates the node on the thread waiting for condition Condition, when the Signal, will shift from the synchronization queue to queue * / 
        / ** waitStatus value to indicate waiting thread iS oN * for condition condition / 
        static int Final cONDITION = -2; 
       / ** waitStatus values, a shared synchronization status is propagated down the expressed unconditional 
        static int pROPAGATE Final = -3; 
        / ** wait state, initially * 0 / 
        volatile waitStatus int; 
        / ** current node precursor nodes * / 
        volatile PREV the node; 
        / ** current node's successor nodes * / 
        volatile Next the node;
        / ** queuing node associated with the current point in the thread * / 
        volatile the Thread Thread; 
        / ** ...... * / 
    }
Copy the code

unlock()

If you understand the process lock, then unlock the seems much easier. Process generally first try to release the lock, if the release succeeds, then see if the head node status is SIGNAL, if the head node is then wake up the next node associated with the thread, so if the release failed to return false to unlock failure. Here we also found out that each time only evoke node associated with the first node of the next thread.

 A flow chart to summarize unfair lock lock acquisition process. 

 

 


If you feel that you have read this article to help, please click the " recommend " button, so that more people can enjoy the happiness acquire knowledge! Because I am entering the workplace, in view of their limited experience, so most of the contents of this blog comes from the summary of existing knowledge of the network , welcome to reprint, comments, we will study together progress! If infringement, please contact me, and effectively protect your rights!

Guess you like

Origin www.cnblogs.com/eryun/p/12044610.html