The underlying implementation principle of Java lock

Source: Internet
Author: User
Tags cas static class

About Java lock on the bottom of the implementation of the principle of a bit deep, reprinted learning.

Lock is written entirely in Java, which is irrelevant to the JVM implementation at this level of Java.

There are many implementation classes of lock in the Java.util.concurrent.locks package, commonly used in Reentrantlock, Readwritelock (Implementation class Reentrantreadwritelock), Its realization all relies on Java.util.concurrent.AbstractQueuedSynchronizer class, realizes the idea to be similar, therefore we take reentrantlock as the explanation cut-in point. Reentrantlock Call Procedure

After observation Reentrantlock all lock interface operations are delegated to a sync class, the class inherits the Abstractqueuedsynchronizer:

Static abstract class Sync extends Abstractqueuedsynchronizer  

Sync has another two subclasses:

Final static class Nonfairsync extends sync  

final static class Fairsync extends Sync  

It is clearly defined to support fair and unjust locks, which are not fair by default.
First, take a look at the call procedure for the Reentrant.lock () method (default non-fair lock):

These pesky template patterns make it hard to see the entire calling process visually, and in fact, through the above invocation process and the Abstractqueuedsynchronizer annotation, Most lock functions are abstracted in Abstractqueuedsynchronizer, and only the Tryacquire method is deferred to the subclass. The semantics of the Tryacquire method is to determine whether a request thread can acquire a lock with a specific subclass, regardless of success or failure, Abstractqueuedsynchronizer will process the following process. Lock Implementation (lock)

Simply put, Abstractqueuedsynchronizer will make all the request threads into a CLH queue, activating its successor when one thread completes (Lock.unlock ()), but the executing thread is not in the queue, While those waiting to execute are all in a blocking state, the explicit blocking of the investigated thread is done by calling Locksupport.park (), while Locksupport.park () calls the Sun.misc.Unsafe.park () local method, and further, Hotspot in Linux by calling the Pthread_mutex_lock function to block the thread to the system kernel.
The queue is shown in figure:

As with synchronized, this is also a virtual queue, there is no queue instance, and there is only a relationship between nodes. It is doubtful why the CLH queue is used. The native CLH queue was used for spin locks, but Doug Lea transformed it into a blocking lock.
When a thread competes for a lock, the line Cheng first attempts to acquire the lock, which is unfair to those already queued in the queue, which is also the origin of the unjust lock, similar to the synchronized implementation, which greatly improves throughput.
If a running thread already exists, the new competitive thread is appended to the tail, in particular the CAS based Lock-free algorithm, because the thread concurrent to the tail call CAs may cause other thread CAs to fail, and the solution is to cycle the CAS until successful. The realization of Abstractqueuedsynchronizer is very exquisite, breathtaking, the details are difficult to fully comprehend the essence, the following detailed description of the implementation process: 2.1 sync.nonfairtryacquire

The Nonfairtryacquire method will be the first method indirectly invoked by the lock method, which is invoked each time the lock is requested.

final  
    Boolean nonfairtryacquire (int acquires) {final Thread current = Thread.CurrentThread ();  
    int c = GetState ();  
            if (c = = 0) {if (compareandsetstate (0, acquires)) {Setexclusiveownerthread (current);  
        return true;  
        } else if (current = = Getexclusiveownerthread ()) {int NEXTC = c + acquires;  
        if (NEXTC < 0)//overflow throw new Error ("Maximum lock count Exceeded");  
        SetState (NEXTC);  
    return true;  
return false; }  

The method first determines the current state, if c==0 indicates that there is no line one thread in the competition for the lock, if no C!=0 indicates that the thread is owning the lock.
If c==0 is found, the initial call value of Acquires,acquires with the CAS setting is 1, each time the thread resets the lock to +1, each unlock-1, but the lock is released for 0 o'clock. If the CAS setting succeeds, you can expect that any other thread to call CAS will not succeed again, and that the current thread has the lock, as well as the running thread, apparently the running thread has not entered the wait queue.
If C!=0 but found that they already have a lock, simply ++acquires and modify the status value, but because there is no competition, so through setstatus modify, rather than CAs, that is, this code to achieve the function of the lock, and achieve very beautiful. 2.2 Abstractqueuedsynchronizer.addwaiter

The Addwaiter method is responsible for wrapping a thread that is currently unable to acquire a lock as a node to add to the team tail:

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;  
}  

Where parameter mode is an exclusive or shared lock, default is NULL, exclusive lock. The action appended to the end of the team is divided into two steps:
If the current team end already exists (Tail!=null), update the current thread to tail using CAs
If the current tail is null or if the thread fails to invoke the CAS Setup team tail, the Enq method continues to set the tail
Here is the Enq method:

Private Node Enq (final node node) {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 method is to cycle through the CAS, even if there is a high concurrency scenario, the infinite loop will eventually successfully append the current thread to the end of the queue (or set the team head). In summary, the purpose of Addwaiter is to append the current thread to the end of the queue through CAs and return the wrapped node instance.

The main reason for wrapping threads as node objects, in addition to using node constructs for virtual queues, is to wrap various thread states with node, which are carefully designed for some numeric values: SIGNAL (-1): The thread's successor line one thread/has been blocked, This successor thread (Unpark) cancelled (1) is to be renewed when the thread is release or cancel: The thread has been canceled CONDITION (-2) because of a timeout or interruption: Indicates that the thread is in a conditional queue, is blocked by calling Condition.await PROPAGATE (-3): Propagate share lock 0:0 represents stateless 2.3 abstractqueuedsynchronizer.acquirequeued

The main function of acquirequeued is to block the thread node (Addwaiter method return value) that has already been appended to the queue, but to try to get the lock by Tryaccquire retry before blocking, and if the retry succeeds, it will not block and return directly

Final Boolean acquirequeued (final node node, int arg) {  
    try {  
        Boolean interrupted = false;  
        for (;;) {  
            final Node p = node.predecessor ();  
            if (p = = head && tryacquire (ARG)) {  
                Sethead (node);  
                P.next = null; Help GC return  
                interrupted;  
            }  
            if (Shouldparkafterfailedacquire (p, node) &&  
                parkandcheckinterrupt ())  
                interrupted = true;  
        }  
    catch (RuntimeException ex) {  
        Cancelacquire (node);  
        Throw ex;  
    }  
}  

Take a closer look at this method is an infinite loop, feeling if p = = head && tryacquire (ARG) conditions do not meet the cycle will never end, of course, there will be no dead loops, The mystery is that the parkandcheckinterrupt of Line 12th will suspend the current thread, blocking the call stack of the thread.

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

As mentioned earlier, Locksupport.park eventually blocked the thread to the system (Linux) kernel. Of course, it's not immediately blocking the thread requesting the lock, and checking the state of the thread, for example, if the thread is in a cancel state, the specific check is in Shouldparkafterfailedacquire:

private static Boolean Shouldparkafterfailedacquire (node pred, node node) {int ws = Pred.waitstatus; if (ws = = node.signal)/* This Node has already set status asking a release * to SIG  
      NAL it, so it can safely park */return true; if (ws > 0) {/* predecessor was cancelled. 
           Skip over predecessors and * indicate retry.  
    * * do {Node.prev = pred = Pred.prev;  
            while (Pred.waitstatus > 0);  
    Pred.next = node; else {/* * Waitstatus must be 0 or PROPAGATE. Indicate that we * need a signal, but don ' t park yet.  
           Caller'll need to * retry to make sure it cannot acquire before.  
      * * Compareandsetwaitstatus (pred, WS, node.signal);  
  return false; }

The principle of inspection is:

Rule 1: If the previous node state is signal, indicating that the current node requires Unpark, the return succeeds, at which point line 12th (parkandcheckinterrupt) of the acquirequeued method will cause the thread to block

Rule 2: If the previous node state is cancelled (WS>0), the predecessor node has been discarded, then backtracking back to a non-canceled forward node, the infinite loop of the return false,acquirequeued method calls the method recursively until rule 1 returns true. Cause thread Blocking

Rule 3: If the former node state is a signal, cancelled, then the state of the previous relay is signal, returning false to the acquirequeued infinite loop, with rule 2

Generally speaking, Shouldparkafterfailedacquire is to rely on the front of the node to determine whether the current thread should be blocked, if the previous relay node in the cancelled state, and then delete these nodes to reconstruct the queue.
At this point, the logic to lock the thread is complete, and the process of unlocking is discussed below. Unlock

A thread that requests a lock that is unsuccessful will be suspended at line 12th of the Acquirequeued method, the code after 12 lines must be unlocked to execute, and if the blocked thread gets unlocked, execute line 13th, set interrupted = True, and then enter an infinite loop.

From the infinite loop of code can be seen, it is not the unlocked thread that is bound to acquire the lock, it must be called in line 6th to Tryaccquire, because the lock is unfair and may be acquired by a newly joined thread, causing the newly awakened thread to be blocked again, a detail that fully embodies the "injustice" The essence. The unlocking mechanism to be introduced will see that the first unlocked thread is the head, so that the P = Head's judgment is almost always successful.

As you can see, the practice of delaying the tryacquire approach to subclasses is very subtle and highly scalable and breathtaking. Of course the subtlety is not the template design pattern, but the careful layout of Doug Lea's lock structure.

The unlock code is relatively simple, mainly embodied in the Abstractqueuedsynchronizer.release and Sync.tryrelease methods:
Class Abstractqueuedsynchronizer

Public final Boolean release (int arg) {  
    if (Tryrelease (ARG)) {  
        Node h = head;  
        if (h!= null && h.waitstatus!= 0)  
            unparksuccessor (h);  
        return true;  
    }  
    return false;  
}  

Class Sync

Protected Final Boolean tryrelease (int releases) {  
    int c = getState ()-releases;  
    if (Thread.CurrentThread ()!= getexclusiveownerthread ())  
        throw new Illegalmonitorstateexception ();  
    Boolean free = false;  
    if (c = = 0) {Free  
        = true;  
        Setexclusiveownerthread (null);  
    }  
    SetState (c);  
    return free;  
}  

Tryrelease is the same as Tryacquire semantics, delaying the logic of how to release to subclasses.

Tryrelease semantics is clear: if a thread locks multiple times, it is released multiple times until status==0 actually releases the lock, and the so-called release lock is set to 0 because there is no competition, so no CAS is used.
The semantics of release are: If a lock can be freed, the first thread (head) of the wake-up queue is awakened, and the specific wake code is as follows:

private void Unparksuccessor (node node) {/* * If status is negative (i.e., possibly needing signal) try * To clear in anticipation of signalling. 
     It is OK if this * fails or if the status is changed by waiting thread.  
    */int ws = Node.waitstatus;   

    if (WS < 0) compareandsetwaitstatus (node, WS, 0);  * * Thread to Unpark are held in successor, which is normally * just the next node. But if cancelled or apparently null, * traverse backwards from tail to find the actual * non-cancelled success 
     Or.  
    */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) Locksupport.unpark (s.thread);  
 }

The meaning of this code is to find the first thread that can be unpark, generally head.next = = Head,head is the first thread, but Head.next may be canceled or NULL, so it is safer to find the first available thread from the back. Seemingly backtracking can lead to performance degradation, but the chances of this happening are small, so there is no performance impact. After that is to inform the system kernel to continue the thread, under Linux is done through the pthread_mutex_unlock. After that, the unlocked thread enters the competitive state as described above. Lock VS Synchronized

Abstractqueuedsynchronizer all blocked threads by constructing a CLH queue based on blocking, while operations on that queue are operated through Lock-free (CAS), but for the thread that has acquired the lock, Reentrantlock realizes the function of bias lock.

The bottom of the synchronized is also a wait queue based on CAS operations, but the JVM is more granular, dividing the wait queues into contentionlist and entrylist, to reduce the thread's column speed and, of course, to achieve a bias lock, From the data structure, there is no essential difference between the two designs. However, synchronized also implements spin locks, which are optimized for different systems and hardware systems, 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 the realization of Read and write lock (Readwritelock), fair or unfair lock; Lock corresponding condition is more convenient and more flexible than wait/notify.

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.