In layman's Java Concurrency (8): Lock mechanism part 3[turn]

Source: Internet
Author: User

Next, this article starts from 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 ()

Gets the lock.

If the lock is not persisted by another thread, it gets the lock and returns immediately, setting the hold count of the lock to 1.

If the current thread has already held the lock, the count is persisted by 1, and the method returns immediately.

If the lock is persisted by another thread, the current thread is disabled for the purpose of thread scheduling, and the thread will remain dormant until the lock is acquired, at which time the lock hold count is set to 1.

As you can see from the documentation above, Reentrantlock is an implementation of reentrant locks. And the internal is entrusted Java.util.concurrent.locks.ReentrantLock.Sync.lock () implementation. Java.util.concurrent.locks.ReentrantLock.Sync are abstract classes, with Java.util.concurrent.locks.ReentrantLock.FairSync and java.util.concurrent . Locks. Reentrantlock.nonfairsync two implementations, which are often called fair locks and unfair locks.

Fair lock and non-fair lock

If a lock is obtained in the order in which it is requested, it is a fair lock, otherwise it is a non-fair lock.

Understand why there is a fair lock and an unfair lock before you understand the internal mechanisms and implementations. A fair lock ensures that a blocked thread can eventually acquire a lock because it is ordered, so it is always possible to obtain a lock in the order in which it is requested. An unfair lock means that after a thread that requests a lock may get a lock before the dormant thread it arranges before it resumes, it is possible to improve concurrency performance. This is because the normally suspended thread restarts with it and it really starts to run, creating a serious delay between the two. Therefore, an unfair lock can be used to complete the operation during this time. This is one of the reasons why unfair locking is better at some times than fair lock performance.

The difference between the two implementations will be described later, and we'll start with a fair lock (Fairsync).

As mentioned earlier Java.util.concurrent.locks.AbstractQueuedSynchronizer (AQS) is the basis of lock, for a fairsync, lock () Call Aqs's acquire (int arg) directly;

Public final void acquire (int arg) gets the object in exclusive mode, ignoring interrupts. This method is implemented by a call at least once tryAcquire(int) , and is returned on success. Otherwise, the thread tryAcquire(int) may be repeatedly blocked or blocked until it has been called to join the queue until it succeeds .

Before introducing the implementation, we need to add the knowledge of the previous section, and for a AQS implementation, it is often necessary to implement the following methods to describe how to lock a thread.

  • tryAcquire(int) An attempt was made to get the state of an object in exclusive mode. This method should query whether it is allowed to get the state of the object in exclusive mode, and if so, get it.

    This method is always called by the thread that executes the acquire. If this method reports a failure, the acquire method can join the thread to the queue (if it has not yet been added to the queue) until another thread has released the signal for that thread. That is, this method is a tentative method, if it is best to acquire the lock successfully, if it is not successful, it is not related, and returns false directly.

  • tryRelease(int) An attempt was made to set the state to reflect a release in exclusive mode. This method is always called by the thread that is executing the release. Releasing the lock may fail or throw an exception, which will be analyzed in detail later.
  • tryAcquireShared(int) 试图在共享模式下获取对象状态。
  • tryReleaseShared(int) 试图设置状态来反映共享模式下的一个释放。
  • isHeldExclusively() 如果对于当前(正调用的)线程,同步是以独占方式进行的,则返回 true

In addition to Tryacquire (int), other methods are described later. First, for Reentrantlock, whether it is a fair or an unfair lock, it is an exclusive lock, which means that a thread can hold a lock at the same time. So 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 more complicated, and we break down the following 4 steps.

      1. If Tryacquire (ARG) succeeds, then there is no problem, the lock has been taken and the entire lock () process is over. If the operation fails, 2.
      2. Create an exclusive node and this node joins the end of the CHL queue. Take action 3.
      3. Spin attempts to acquire a lock, which fails based on the previous node to decide whether to suspend (Park ()) until the lock is successfully acquired. Take action 4.
      4. If the current thread has been interrupted, the current thread (clears the interrupt bit) is interrupted.

This is a more complicated process, we step by step one analysis.

Tryacquire (acquires)

For a fair lock, it is implemented in the following way:

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, the previous description has a state for Aqs that describes how many threads are currently holding locks. Since Aqs supports shared locks (such as read-write locks, which will continue later), this is state>=0 here, but because Reentrantlock is an exclusive lock, it may be understood here as 0<=state,acquires=1. IsFirst (current) is a very complex logic, including the kick out of useless nodes and other complex processes, for the time being, the general meaning is to judge whether Aqs is empty or if the front-end is in the queue header (in order to differentiate between fair and unfair lock).

      1. If the current lock has other threads holding, c!=0, do 2. Otherwise, if the current thread is in the Aqs queue header, attempt to set the Aqs status State to acquires (equal to 1), and after success, AQS exclusive line Cheng returns true for the current thread, otherwise 2. Here you can see that compareandsetstate is using CAS operations.
      2. Determine if the current thread is the same as the exclusive thread of the AQS, if the same, then the current state bit is added 1 (here +1 after the result is a negative number will say, here to ignore it), modify the status bit, return True, otherwise 3. Instead of setting the current status bit to 1, change it to the old value of +1? This is because the Reentrantlock is a reentrant lock, and the same thread is held at 1 per second.
      3. returns FALSE.

Compare the Tryacquire implementation of the non-fair lock java.util.concurrent.locks.ReentrantLock.Sync.nonfairTryAcquire (int), a fair lock more than one to determine whether the current node is in the queue header, This ensures that the order in which locks are acquired is determined in the order in which they are requested (with the exception of multiple fetch locks on the same thread).

Now look back at the lock () method of fair lock and non-fair lock. The fair lock has only one sentence acquire (1); The call to the non-fair lock is as follows:

Final void Lock () {
if (compareandsetstate (0, 1))
Setexclusiveownerthread (Thread.CurrentThread ());
Else
Acquire (1);
}

It is clear that an unfair lock holds a lock for the first time it acquires a lock, or another thread releases the lock (possibly waiting), takes precedence over compareandsetstate (0,1) and then sets Aqs exclusive thread, which is sometimes more efficient than acquire (1) sequential check lock hold. Even if the lock is re-entered, that is, compareandsetstate (0,1) fails, but the current thread holds the lock, the unfair lock is not a problem.

Addwaiter (Mode)

Tryacquire failure means going into the queue. At this point, node Aqs in the queue is starting to work. In general, AQS supports exclusive and shared locks, whereas an exclusive lock in node means that the condition (Condition) queue is empty (related concepts are described in the previous article). There are two constants in the Java.util.concurrent.locks.AbstractQueuedSynchronizer.Node,

Static final Node EXCLUSIVE = null; Exclusive node mode

Static final Node SHARED = new node (); Shared node mode

Mode in Addwaiter (mode) is the node pattern, that is, the shared or exclusive lock mode.

The previous emphasis on Reentrantlock is the 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;
}

Above is a node as part of a queue. The new node is currently returned directly only if the queue is not empty and the new node is inserted in the trailer successfully. Otherwise enter Enq (Node) to operate.

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 Enq (node) Go queue operation implements the algorithm for the CHL queue, creating a header node if it is empty, and then comparing whether the tail of the nodes is a change to determine whether the CAS operation succeeds, and only if it succeeds, points to the new node for the next node that is not a node. You can see that this is still a CAS operation.

Acquirequeued (Node,arg)

Spin the request lock, suspend the thread if possible until the lock is received, and return the current thread if it has been interrupted (if Park () has a interrupted interrupt bit).

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

The following analysis needs to use the state description of the last point. The acquirequeued process is this:

      1. If the current node is the head node of the Aqs queue (if the first node is the dump node or the puppet node, then the second node is actually the head node), try to get the lock Tryacquire (ARG) here. If successful, the head node is set to the current node (regardless of whether the first node is a dump node), and the interrupt bit is returned. Otherwise proceed to 2.
      2. Detects if the current node should park (), if Park () should suspend the current thread and return the current thread break bit. Take action 1.

Whether a node is the park () is the key, which is determined by the method Java.util.concurrent.locks.AbstractQueuedSynchronizer.shouldParkAfterFailedAcquire (node, Node) is implemented.

Private static Boolean Shouldparkafterfailedacquire (node pred, node node) {
    int s = Pred.waitstatus;
    if (S < 0) return true;
    if (S > 0) {
        do {
             Node.prev = pred = Pred.prev;
       } while (Pred.waitstatus > 0);
        pred.next = node;
   } else compareandsetwaitstatus (pred, 0, node.signal);
    return false;
}

      1. If the wait state of the previous node is waitstatus<0, that is, the previous node has not acquired a lock, then returning TRUE indicates that the current node (thread) should be Park (). Otherwise proceed to 2.
      2. If the wait state of the previous node waitstatus>0, that is, the previous node is cancelled, then the previous node is removed, recursively this operation until all the previous node waitstatus<=0, 4. Otherwise proceed to 3.
      3. The previous node waits for state waitstatus=0, modifies the previous node state bit to Singal, indicates that there is a node waiting for you to process, and needs to decide whether the park () depends on its wait state. Proceed to 4.
      4. Returns false, indicating that the thread should not park ().

Selfinterrupt ()

private static void Selfinterrupt () {
Thread.CurrentThread (). interrupt ();
}

If the thread has been interrupted (or blocked) (such as manual interrupt () or timeout, etc., then interrupt again, interrupt two times means to clear the interrupt bit).

Basically the whole lock.lock () is such a process. In addition to the lock () method, there are lockinterruptibly ()/trylock ()/unlock ()/newcondition (), etc., which are described in the following chapters.

In layman's Java Concurrency (8): Lock mechanism part 3[turn]

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.