Java concurrency-lock mechanism (iii) Locking Principle (lock. Lock)

Source: Internet
Author: User

From: http://www.blogjava.net/xylz/archive/2010/07/07/325410.html

Next, this article starts with lock. Lock/unlock. In special cases, all programs, APIs, and documents are based on JDK 6.0.

Public void java. util. Concurrent. locks. reentrantlock. Lock ()

Obtain the lock.

If the lock is not kept by another thread, the lock is obtained and immediately returned. The lock Hold count is set to 1.

If the current thread has kept the lock, it will keep the count plus 1, and the method returns immediately.

If the lock is maintained by another thread, the current thread is disabled for the purpose of thread scheduling, and the thread remains in sleep state until the lock is obtained, the lock Hold count is set to 1.

From the above document, we can see that reentrantlock is the implementation of reentrant locks. Internally, java. util. Concurrent. locks. reentrantlock. Sync. Lock () is implemented. Java. util. concurrent. locks. reentrantlock. sync is an abstract class with Java. util. concurrent. locks. reentrantlock. fairsync and Java. util. concurrent. locks. reentrantlock. nonfairsync implements both fair locks and unfair locks.

Fair lock and unfair lock

If a lock is obtained in the Request order, it is a fair lock. Otherwise, it is an unfair lock.

Before learning about internal mechanisms and implementations, let's take a look at why there are fair locks and unfair locks. The fair lock ensures that a blocked thread can finally obtain the lock. Because it is ordered, the lock can always be obtained in the Request order. Unfair locks mean that the thread that requests the lock may obtain the lock before the sleep thread that is previously arranged recovers, which may improve the concurrency performance. This is because normally a suspended thread restarts and it actually starts to run, causing serious latency between the two. Therefore, unfair locks can be used to complete the operation. This is one of the reasons why unfair locks sometimes have better performance than fair locks.

The differences in implementation between the two will be described later. We will first start with the fair lock (fairsync.

As mentioned earlierJava. util. Concurrent. locks. abstractqueuedsynchronizer (AQS)
Is the basis of lock. For a fairsync, lock () directly calls AQS's acquire (INT Arg );

Public final void acquire (INT Arg)
 
Get the object in exclusive mode and ignore the interruption. Call at least once

tryAcquire(int)


 
To implement this method, and return when the method is successful. Otherwise, call

tryAcquire(int)


 
Add the thread to the queue. The thread may be blocked repeatedly or not blocked.

Before introducing the implementation, we need to add the knowledge in the previous section. For the implementation of an AQS, we usually need to implement the following methods to describe how to lock the thread.

  • tryAcquire(int)
     

    Tries to get the object status in exclusive mode. This method should be used to check whether it allows an object to be obtained in exclusive mode. If so, it is obtained.

    This method is always called by the thread that executes acquire. If this method reports a failure, the acquire method can add the thread to the queue (if it has not been added to the queue) until it obtains the signal that another thread has released the thread. That is to say, this method is a tentative method. If the lock is obtained successfully, it is best. If the lock is not obtained successfully, it does not matter, and false is returned directly.

  • tryRelease(int)
     

    Try to set the status to reflect a release in exclusive mode. This method is always called by a thread that is being released. Releasing the lock may fail or throw an exception, which will be analyzed later.
  • Tryacquireshared (INT)
     
    Try to get the object status in shared mode.
  • Tryreleaseshared (INT)
     
    Try to set the status to reflect a release in shared mode.
  • Isheldexclusively ()
     
    If the synchronization is performed exclusively for the current (called) thread, the system returns
    true
    .
  • In addition to tryacquire (INT), other methods will be described later. First, for reentrantlock, both fair lock and unfair lock are exclusive locks, that is, a thread can hold the lock at the same time. Therefore, for acquire (INT Arg), Arg = 1. The implementation of acquire in AQS is as follows:

    Public final void acquire (INT Arg ){
    If (! Tryacquire (ARG )&&
    Acquirequeued (addwaiter (node. Exclusive), ARG ))
    Selfinterrupt ();
    }

    This looks complicated. Let's take the following four steps.

    1.  
      1. If tryacquire (ARG) succeeds, there is no problem. The lock has been obtained and the entire lock () process ends. If the request fails, perform operation 2.
      2. Create an exclusive node and add it to the end of the chlorophyll queue. Operation 3.
      3. The spin attempts to obtain the lock, and the Failure determines whether to suspend (Park () based on the previous node until the lock is successfully obtained. Operation 4.
      4. If the current thread has been interrupted, the current thread is interrupted (the interrupt bit is cleared ).

    This is a complicated process. We will analyze it step by step.

    Tryacquire (acquires)

    For a fair lock, the implementation method is as follows:

    Protected final Boolean tryacquire (INT acquires ){
    Final thread current = thread. currentthread ();
    Int c = getstate ();
    If (C = 0 ){
    If (isfirst (current )&&
    Compareandsetstate (0, acquires )){
    Setexclusiveownerthread (current );
    Return true;
    }
    }
    Else if (current = getexclusiveownerthread ()){
    Int nextc = C + acquires;
    If (nextc <0)
    Throw new error ("Maximum lock count exceeded ");
    Setstate (nextc );
    Return true;
    }
    Return false;
    }
    }

    In this Code, we described the existence of a State for AQS to describe how many threads hold the lock. Because AQS supports shared locks (such as read/write locks, we will continue to talk about them later), State> = 0 here. However, because reentrantlock is an exclusive lock, it may be understood as 0 <= state, acquires = 1. Isfirst (current) is a complex logic, including kicking out useless nodes and other complex processes, in general, it means determining whether AQS is null or whether the current thread is in the queue header (to distinguish between fair and unfair locks ).

    1.  
      1. If the current lock is held by another thread, C! = 0. Perform operation 2. Otherwise, if the current thread is in the AQS queue header, set the AQS state to acquires (equal to 1). If the AQS exclusive thread is set to the current thread, true is returned, otherwise, perform 2. Here we can see that compareandsetstate uses the CAS operation.
      2. Determine whether the exclusive thread of the current thread is the same as that of AQS. If the exclusive thread is the same, add the current status bit to 1 (here the result after + 1 is a negative number will be discussed later, and ignore it now ), if the status bit is modified, true is returned. Otherwise, 3 is performed. The reason is not to set the current status bit to 1, but to the old value + 1? This is because reentrantlock Is A reentrant lock, and each time the same thread holds the lock, it will increase by 1.
      3. Returns false.

    Implementation of tryacquire with unfair locks in Java. util. concurrent. locks. reentrantlock. sync. nonfairtryacquire (INT), the fair lock has an additional one to judge whether the current node is in the queue header, this ensures that the order in which the lock is obtained is determined based on the request lock sequence (except for multiple locks in the same thread ).

    Now let's look back at the lock () method of fair lock and non-fair lock. The fair lock has only one acquire (1), but the call of the non-fair lock is as follows:

    Final void lock (){
    If (compareandsetstate (0, 1 ))
    Setexclusiveownerthread (thread. currentthread ());
    Else
    Acquire (1 );
    }

    Obviously, when an unfair lock is obtained for the first time or other threads release the lock (may wait), compareandsetstate () is used first and then AQS exclusive thread is set to hold the lock, this is sometimes more efficient than acquire (1) sequential check lock hold. Even if the re-entry lock, that is, compareandsetstate () fails, but the current thread holds the lock, there is no problem with the unfair lock.

    Addwaiter (Mode)

    If tryacquire fails, it means the queue is in. In this case, nodes in the queue of AQS start to play a role. Generally, AQS supports exclusive locks and shared locks, while exclusive locks in node mean that the condition queue is empty (related concepts have been introduced in the previous article ). There are two constants in Java. util. Concurrent. locks. abstractqueuedsynchronizer. node,

    Static final node exclusive = NULL; // exclusive node Mode

    Static final node shared = new node (); // shared node Mode

    The Mode in addwaiter (mode) is the node mode, that is, the shared lock or the exclusive lock mode.

    We have repeatedly stressed that reentrantlock is an exclusive lock mode.

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

    The above is a part of a node, such as a queue. The new node is returned only when the queue is not empty and the new node is successfully inserted to the end. Otherwise, Enq (node) is enabled.

    Private node Enq (final 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;
    }
    }
    }
    }

    Enq (node) uses the queuing Operation to implement the algorithm of the chlorophyll queue. If it is null, it creates a header node and checks whether the node tail is changed to determine whether the CAS operation is successful, the next node that is not a node points to the new node only after the node is successfully executed. The CAS operation is still performed here.

    Acquirequeued (node, ARG)

    Spin the request lock. If possible, the thread is suspended until the lock is obtained, and the system returns whether the current thread has been interrupted (if Park () has been interrupted and there is an interrupted interrupt bit ).

    Final Boolean acquirequeued (final 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;
    }
    }

    The following analysis requires the State Description of the last node. The acquirequeued process is as follows:

    1.  
      1. If the current node is the head node of the AQS Queue (if the first node is a dump node or a slave node, the second node is actually a head node ), try to obtain the tryacquire (ARG) lock here ). If it succeeds, the header node is set to the current node (no matter whether the first node is a dump node), and the interrupt bit is returned. Otherwise, perform 2.
      2. Checks whether the current node should be Park (). If it should be Park (), the current thread is suspended and the current thread interrupt bit is returned. Operation 1.

    Whether a node is the park () is the key, which is implemented by Java. util. Concurrent. locks. abstractqueuedsynchronizer. shouldparkafterfailedacquire (node, node.

    Private Static Boolean shouldparkafterfailedacquire (node Pred, node ){
    Int S = Pred. waitstatus;
    If (S <0) return true;
    If (S> 0 ){
    Do {
    Node. Prev = Pred. Prev;
    } While (Pred. waitstatus> 0 );
    Pred. Next = node;
    } Else compareandsetwaitstatus (Pred, 0, node. Signal );
    Return false;
    }

    1.  
      1. If the waiting status of the previous node is waitstatus <0, that is, the previous node has not obtained the lock, return true, indicating that the current node (thread) should be Park. Otherwise, perform 2.
      2. If the waiting status of the previous node is waitstatus> 0, that is, the previous node is cancelled, remove the previous node, recursion of this operation until the waitstatus of all previous nodes <= 0, 4. Otherwise, perform 3.
      3. The waiting status of the previous node is waitstatus = 0, and the status bit of the previous node is singal, indicating that a node is waiting for you to process it. You need to determine whether to choose the park () based on its waiting status (). 4.
      4. Returns false, indicating that the thread should not be Park ().

    Selfinterrupt ()

    Private Static void selfinterrupt (){
    Thread. currentthread (). Interrupt ();
    }

    If the thread has been interrupted (or blocked) (for example, manual interrupt () or timed out), the thread will be interrupted again. The interrupted bit is cleared twice ).

    The entire lock. Lock () process is like this. In addition to the lock () method, there are lockinterruptibly ()/trylock ()/unlock ()/newcondition () and so on, which will be described in the following chapters.

     


    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.