Deep analysis of the implementation of Reentrantlock Fair lock and non-fair lock source code

Source: Internet
Author: User
Tags cas prev wrapper

In this paper, a fair and non-fair lock locking into the main line, analysis of the entire lock process. Prepare a knowledge brief

Reentrantlock class Diagram:

Nonfairsync Inheritance Relationship:


Node node: As the wrapper class that gets the lock failure thread, the thread reference is combined and implemented as a FIFO bidirectional queue. The following image describes the properties of the node node.

Creation of Locks

Non-fair Lock (default)

Final Reentrantlock lock = new Reentrantlock ();
Final Reentrantlock lock = new Reentrantlock (false);

Fair lock

Final Reentrantlock lock = new Reentrantlock (true);
Non-fair lock and lock process
    Static final class Nonfairsync extends Sync {
        private static final long serialversionuid = 7316153563782823691L;

        /**
         * Logic: Multiple threads call the Lock () method, if the current state is 0, the description is unlocked, then only one thread will get the CAS lock and set this thread as an exclusive lock thread. Other threads call the acquire method to compete for a lock (all subsequent synchronizations or hangs are added to the synchronization queue). When another thread a comes in lock, exactly the previous thread is releasing the lock, then a happens to get the lock first before all the threads in the synchronization queue are waiting to acquire the lock. This means that all threads that have not yet been fetched in the synchronization queue are absolutely guaranteed to acquire the lock serially. The following code explains.
         * */
        final void Lock () {
            if (compareandsetstate (0, 1))
                Setexclusiveownerthread ( Thread.CurrentThread ());
            else
                acquire (1);
        }
/**
* This is the protected method for Aqs, which allows subclasses to rewrite
*
        /protected Final Boolean tryacquire (int acquires) {
            return Nonfairtryacquire (acquires);
        }
    }

First look at the Acquire method:
Logic: The Tryacquire method still attempts to acquire a lock (fast acquire lock mechanism), successfully returns false, and if not successful, wraps the thread into node to join the tail of the synchronization queue. And does not respond to interrupts in the queue. Node.exclusive represents exclusive mode.

    The public final void acquire (int arg) {
    //Tryacquire () method is also an opportunity for a new thread to jump in the queue for a second time.
        if (!tryacquire (ARG) &&
            acquirequeued (Addwaiter (node.exclusive), arg))
            selfinterrupt ();
    }

See Addwaiter Logic: Node wrapper the current thread pred tail pointer is not NULL, that is, the queue is not empty, then rapid CAs is added to the tail node if the queue is empty, or if the CAS setting fails, the call Enq is queued

Private node Addwaiter (node mode) {
        node node = new Node (Thread.CurrentThread (), mode);
        Try the fast path of Enq; Backup to full Enq on Failure
        Node pred = tail;
        if (pred! = null) {
            Node.prev = pred;
            if (Compareandsettail (pred, node)) {
                pred.next = node;
                return node;
            }
        }
        Enq (node);
        return node;
    }

Here is the Enq method:

Private node Enq (final node node) {for
        (;;) {
            Node t = tail;
            if (t = = null) {//Determine if the queue is empty
                if (Compareandsethead (new Node ()))
                    tail = head;//head and tail together point to header
                    //Note This node is not the current thread wrapper junction. c6/>} else {//cas added and allowed to fail, walk for (;;)
                Node.prev = t;
                if (Compareandsettail (t, node)) {
                    t.next = node;
                    return t;}}}
    

After the team is finished, look at the acquirequeued logic:

Failed marks whether the final acquire is successful, interrupted whether the tag has been suspended. Notice that for (;;) the only condition that jumps out is if (p = = head && tryacquire (ARG)) that is, the current thread node is the head node and acquires the lock successfully. That is, only the P node has all the preceding nodes executed, and p = = Head && node requests succeed.

Final Boolean acquirequeued (final node node, int arg) {Boolean failed = true;
            try {Boolean interrupted = false; for (;;)
                {//Get the predecessor node final node P = node.predecessor (); If the first loop succeeds then the returned interrupted is false and does not require self-interruption. Otherwise the description has been suspended (returning true) before it gets to the synchronization state, so you need to break yourself and see the code in the Acquire method.
                    Note: Interrupts are not interrupted by the execution of the current thread if (p = = head && tryacquire (ARG)) {Sethead (node); P.next = null;
                    Help GC failed = false;
                return interrupted; }//If the current node thread is pending, call Parkandcheckinterrupt Pending, interrupted set to True, if (Shouldparkafterfai
            Ledacquire (p, node) && parkandcheckinterrupt ()) interrupted = true;
        }} finally {if (failed)//If Tryacquire an exception then cancels the acquisition of the current node Cancelacquire (node); }
    }

Look at the logic of the Shouldparkafterfailedacquire method:

   private static Boolean Shouldparkafterfailedacquire (node pred, node node) {
        int ws = Pred.waitstatus;
        if (ws = = node.signal)/
      * If the predecessor
      node is a SIGNAL state, then the current node is executed to wake up the subsequent nodes, at which point the current node can be safely suspended, no unnecessary for (;;), the predecessor node will naturally be notified.
      */
            return true;
        if (ws > 0) {
      ////   If ws>0 indicates that the predecessor node is the node that is being canceled by itself to synchronize (only the thread itself can cancel itself). Then the Do and loop is looking for Waitstatus < 0 in the direction of the head node, meaning to remove the thread that has self-canceling the request synchronization state in the FIFO queue. Do
            {
                Node.prev = pred = Pred.prev,
            } while (Pred.waitstatus > 0);
            pred.next = node;
        } else {

             //If the other state waitstatus is not 0 or propagate, meaning that the current node requires a signal but is not yet park, the caller must ensure that the synchronization state cannot be obtained until it is suspended, so return false. and forcibly set the predecessor node to signal. CAS is set because the pred thread also operates Cancelacquire to cancel itself and the node thread to compete for pred operations.
            compareandsetwaitstatus (pred, WS, node.signal);
        }
        return false;
    }

Look at the Nonfairsync in Reentrantlock to rewrite the Synchronizer method Tryacquire:

Protected Final Boolean tryacquire (int acquires) {
            return Nonfairtryacquire (acquires);
        }

Here is Nonfairtryacquire:

Final Boolean nonfairtryacquire (int acquires) {
            final Thread current = Thread.CurrentThread ();
            int c = getState ();
            if (c = = 0) {//If no thread acquires the lock, the current thread CAs acquires the lock. and set itself as the exclusive thread for the current lock
                if (compareandsetstate (0, acquires)) {
                    setexclusiveownerthread (present);
                    return true;
                }
            }
            else if (current = = Getexclusiveownerthread ()) {//If there is a lock contention, determine if the thread that acquires the lock is the current thread, and if so it is based on the meaning of reentrant, so that currently state+1;
                int NEXTC = c + Acquires;
                if (NEXTC < 0)//overflow
                    throw new Error ("Maximum lock count Exceeded");
                SetState (NEXTC);
                return true;
            } If it is not the current thread, you cannot get the synchronization status
            return false;
        
the locking process of the fair lock

The realization of fair lock to Tryacquire:

 Static final class Fairsync extends Sync {private static final long serialversionuid = -3000897897090466540l;// The lock method compares the unfair lock, without the if else means that the new thread does not have the chance to jump in the queue, all the threads must be thrown to the end of the line, the acquire method will call Tryacquire queue first, like an unfair lock (if the queue is empty or if it is the head,
        Then it is possible to succeed, the queue is not empty so sure to be thrown to the tail of the queue, insert a yarn.
        Final void Lock () {acquire (1); }//Only the node thread with the queue is empty or the current Tryacquire is head protected final Boolean tryacquire (int acquires) {final T
            Hread current = Thread.CurrentThread (); int c = getState ();//Get the current synchronization state, if it is a lock-free State, then the Hasqueuedpredecessors method logic, the logical meaning is: The current queue is empty or itself is the head node in the synchronization queue.
            If the condition is met, the CAS gets the synchronization state and sets the current exclusive thread.  if (c = = 0) {if (!hasqueuedpredecessors () && compareandsetstate (0, acquires))
                    {Setexclusiveownerthread (current);
                return true;
                }}//re-enter lock logic and unfair lock do not interpret else if (current = = Getexclusiveownerthread ()) { int NextC = C + acquires;
                if (NEXTC < 0) throw new Error ("Maximum lock count Exceeded");
                SetState (NEXTC);
            return true;
        } return false; }
    }

See Hasqueuedpredecessors ()
and unfair lock compared with this method of logic, also think that there is no new thread in the queue, to ensure the acquisition of a fair lock serialization.

Public Final Boolean hasqueuedpredecessors () {
        node t = tail;//Read fields in reverse initialization order
        Node h = head;
        Node s;
        return h! = T &&
            ((s = h.next) = = NULL | | S.thread! = thread.currentthread ());
    }
The release logic of fair lock and non-fair lock

The release logic of fair lock and non-fair lock is consistent through sync.release (1);
Reentrantlock in Unlock ()

public void Unlock () {
        sync.release (1);
    }

The release method in Abstractqueuedsynchronizer:
After release, call the Unparksuccessor () method to wake the successor node.

    Public final Boolean release (int arg) {
        if (Tryrelease (ARG)) {
            Node h = head;//The current head node is the head node in the synchronization queue
            //and is not necessarily wrapped The execution thread that is currently releasing the lock, because it may be an unfair lock release, is not inserted in the queue at all. Get synchronized
            if (h! = NULL && H.waitstatus! = 0)
                unparksuccessor (h);
            return true;
        }
        return false;
    }

Logic tryrelease to release the lock:

  Protected Final Boolean tryrelease (int releases) {
            int c = getState ()-releases;  only the thread that obtains the lock can release the lock
            if (Thread.CurrentThread ()! = Getexclusiveownerthread ())
                throw new Illegalmonitorstateexception ();
            Boolean free = false;
            if (c = = 0) {//c==0 description already unlocked free
                = true;
                Setexclusiveownerthread (null);
            }
            SetState (c);
            Otherwise, update the state to State-1, that is, lock the number of times, you have to release how many times, lock unlock paired use.

            return free;
        }

See Unparksuccessor method Logic:

   private void Unparksuccessor (node node) {

        int ws = Node.waitstatus;
        if (WS < 0)//is also because the node thread is competing with the current thread, node itself can also modify its own state, so CAS
            compareandsetwaitstatus (node, WS, 0);
        Node s = node.next;
        if (s = = NULL | | s.waitstatus > 0) {
            s = null;
            Find the node that is closest to the head node and is in a non-canceled state from the tail node. Leave a question. The following explains for
            (node t = tail; t! = null && t! = Node; t = t.prev)
                if (t.waitstatus <= 0)
                    s = t;
  }
        //s is the successor node because the successor node is likely to satisfy the condition if it satisfies the S!=null direct unpark subsequent node.
        if (s! = null)
            Locksupport.unpark (S.thread);
    }

Why do I look forward from tail in the Unparksuccessor method for thread nodes that need to be unpark? Let's take a look at the Enq method:

Private node Enq (final node node) {for
        (;;) {
            Node t = tail;
            if (t = = null) {//Must initialize
                if (Compareandsethead (new Node ()))
                    tail = head;
            } else {
                Node.prev = t ;
                if (Compareandsettail (t, node)) {
                    t.next = node;
                    return t;}}}
    

First Node.prev = t new node prev point to the tail node, a few CAs will tail point to the new node node, when this step is finished, before T.next = node execution, that is, the previous tail node of the next pointer has not pointed to the new node, at this time There is no problem, I think it is wrong to say that there is a problem online. Because when acquirequeued (Addwaiter (node.exclusive), Arg) is called, the IF (p = = head && tryacquire (arg)) P = = Head in the method is not satisfied and then called Shouldparkafterfailedacquire () deletes the canceled node and marks the pre-node with the status signal, then the P node is head and enters the IF statement block without any problem.

the root knot of the problem lies in the Cancelacquire () method

 private void Cancelacquire (node node) {//Ignore if node doesn ' t exist if (Node = = null) ret

        Urn

        Node.thread = null;
        Skips all the canceled pre-node nodes pred = Node.prev;

        while (Pred.waitstatus > 0) node.prev = pred = Pred.prev;

        Node prednext = Pred.next;

        Node.waitstatus = node.cancelled; If the tail node is directly deleted if (node = = Tail && compareandsettail (node, pred)) {Compareandsetnext (pred, Pre
        Dnext, NULL); } else {//) If you need to wake up the next node of the node you want to cancel, set Pred's next pointer.
            Because the previous while loop is just setting the forward pointer int ws; If the predecessor node State is signal | | If it is not CAS set to signal then the next execution will naturally wake up the next node if (pred! = head && (ws = pred.waitstatus) = = Nod
                 e.signal | | (ws <= 0 && compareandsetwaitstatus (pred, WS, node.signal)))
                && Pred.thread! = null) {Node next = Node.next; if (NEXT! = null && next.waitstatus <= 0)//Set Next-link Compareandsetnext (pred, Prednext, next);
            } else {//If it is the head node p = = Head unparksuccessor (node); } node.next = node;
        Help GC//key issue is here to end up with the acquire node's next pointer pointing to its own ... }
    }

Then the problem comes, because each node in the queue can cancel itself at any time, and once we go from the Unpark to the point where the thread suddenly cancels itself out, and the next pointer points to itself, then we have no way to go back and find the thread that satisfies the condition to Unpark. So from the tail forward to look for there will be no problem ~

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.