In layman's Java Concurrency (9): Lock mechanism part 4[turn]

Source: Internet
Author: User
Tags lock queue prev

This section describes lock release Lock.unlock ().

Release/tryrelease

The unlock operation actually invokes the AQS release operation, releasing the held lock.

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

The previous tryrelease (ARG) operation, which always attempts to release the lock, if successful, indicates that the lock is actually held by the current thread, then see if the head node in the AQS queue is empty and can be woken up. If possible, wake up the successor node (the next non-cancelled node, which is analyzed below).

For an exclusive lock, java.util.concurrent.locks.ReentrantLock.Sync.tryRelease (int) shows how to try to release the lock (tryrelease) operation.

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

The whole tryrelease operation is this:

      1. Determines whether the thread holding the lock is the current thread, and if not, throws Illegalmonitorstateexeception (), because one thread is unable to free the lock held by another thread (otherwise the lock loses its meaning). Otherwise proceed to 2.
      2. Reduce the number of Aqs state bits to be freed (always 1 for exclusive locks), if the remaining state bit 0 (that is, no thread holds the lock), then when the front thread is the last one holding the lock, empty the exclusive thread that holds the lock Aqs. Proceed to 3.
      3. Writes the remaining status bits back to Aqs and returns true if no thread holds the lock, otherwise it is false.

As you can see from the analysis in the previous section, this c==0 determines whether the lock is completely released. Since Reentrantlock is a reentrant lock, the same thread may hold the lock multiple, so if and only if the last thread holding the lock releases the lock, the exclusive line holding the lock in the AQS Cheng empty, so that the next operation needs to wake up the next AQS that needs to be locked. node, otherwise it simply reduces the lock-held counter and does not change other operations.

When the tryrelease operation succeeds (that is, the lock is completely released), the release operation checks to see if the next successor node needs to be woken up. The premise here is that the AQS queue's head node requires a lock (waitstatus!=0), and if the head node needs a lock, it begins to detect if the next successor node needs a lock operation.

In the previous section, when the acquirequeued operation is completed (the lock is taken), the node that currently holds the lock is set as the head node, so once the head node releases the lock, it is necessary to look for the next successor node that needs to be locked for the head junction and wake it up.

private void Unparksuccessor (node node) {
At this point, node is the head node that needs to release the lock.

Empty the waitstatus of the head node, which means no more locks are needed.
Compareandsetwaitstatus (node, node.signal, 0);

The next node of the starting point is looking for a successor node, and the waitstatus>0 (that is, cancelled nodes) are removed from the Aqs queue if and only if the successor node's waitstatus<=0 is the effective successor node.
Instead of looking for head->tail, we are looking for the last valid node from Tail->head.
EXPLANATION here http://www.blogjava.net/xylz/archive/2010/07/08/325540.html#377512

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 a valid successor node is found, the node thread is awakened
if (s! = null)
Locksupport.unpark (S.thread);
}

Here again to find out the process of acquirequeued . In contrast unparksuccessor, once the successor node of the head node is awakened, the successor node attempts to acquire the lock (in acquirequeued , node is the effective successor, p is the head node that wakes it), If successful, the head node is set to itself and the predecessor node of the header is emptied so that the predecessor (which is obsolete) can be released by the GC.

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

In Sethead , the predecessor node of the header is emptied and the thread of the head node is emptied for a better GC to prevent memory leaks.

private void Sethead (node node) {
Head = node;
Node.thread = null;
Node.prev = null;
}

Compared to the lock () operation, the Unlock () operation is relatively straightforward, primarily to release the response resource and wake up the active successor node in the AQS queue. This attempts to acquire the lock in the order in which it was requested.

The entire lock ()/unlock () process is complete, and we look back at the Fair Lock (Fairsync) and the Non-fair lock (Nonfairsync).

Fair locks and unfair locks are only different when acquiring locks, others are the same.

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

In the code above the unfair lock is always the first attempt is currently a thread to hold the lock, once there are no threads hold the lock, then the unfair lock on the overbearing attempt to "take the lock for own". If you fail to seize the lock, just as honestly as the fair lock to line up.

In other words, fair and unfair locks only differ before entering the AQS CLH queue, and once in the queue, all threads request locks in the order in which they are first served in the queue.

Condition

The condition variable is to a large extent to solve the problem that Object.wait/notify/notifyall is difficult to use.

A condition (also known as a conditional queue or condition variable ) provides a means for a thread to suspend the thread (that is, let it "wait") until another thread that a state condition might now be true notifies it. Because access to this shared state information occurs in different threads, it must be protected so that a form of lock is associated with that condition. The primary property for waiting to provide a condition is to atomically release the associated lock and suspend the current thread as if it were Object.wait done.

The API description above indicates that the condition variable needs to be bound to the lock, and that multiple condition need to be bound to the same lock. As mentioned in the previous Lock , the method to get a condition variable is lock.newcondition ().

void await () throws interruptedexception;
void awaituninterruptibly ();
Long Awaitnanos (long nanostimeout) throws interruptedexception;
Boolean await (long time, Timeunit unit) throws Interruptedexception;
Boolean awaituntil (Date deadline) throws Interruptedexception;
void signal ();
void Signalall ();

The above is the method defined by the Condition interface,await* corresponds to object.wait,signal corresponds to object.notify, signalall corresponds to object.notifyall. Specifically, the Condition interface changes the name to avoid confusion with the semantics and use of Wait/notify/notifyall in object, because Condition also has Wait/notify/notifyall method.

Each lock can have arbitrary data Condition object,Condition is bound with lock , so there is lock Fairness Characteristics: If it is a fair lock, the thread is released from the condition.await in the FIFO order, and if it is a non-fair lock, then the subsequent lock contention does not guarantee the FIFO order.

An example of a model using condition to implement producer consumers is as follows.

Package xylz.study.concurrency.lock;

Import java.util.concurrent.locks.Condition;
Import Java.util.concurrent.locks.Lock;
Import Java.util.concurrent.locks.ReentrantLock;

public class Productqueue<t> {

Private final t[] items;

Private Final lock lock = new Reentrantlock ();

Private Condition Notfull = Lock.newcondition ();

Private Condition Notempty = Lock.newcondition ();

//
private int head, tail, count;

Public productqueue (int maxSize) {
Items = (t[]) new object[maxsize];
}

Public Productqueue () {
This (10);
}

public void put (T t) throws Interruptedexception {
Lock.lock ();
try {
while (count = = getcapacity ()) {
Notfull.await ();
}
Items[tail] = t;
if (++tail = = Getcapacity ()) {
tail = 0;
}
++count;
Notempty.signalall ();
} finally {
Lock.unlock ();
}
}

Public T take () throws Interruptedexception {
Lock.lock ();
try {
while (count = = 0) {
Notempty.await ();
}
T ret = Items[head];
Items[head] = NULL;//GC
//
if (++head = = Getcapacity ()) {
Head = 0;
}
--count;
Notfull.signalall ();
return ret;
} finally {
Lock.unlock ();
}
}

public int getcapacity () {
return items.length;
}

public int size () {
Lock.lock ();
try {
return count;
} finally {
Lock.unlock ();
}
}

}

In this example, consuming take () requires that the queue is not empty, if it is empty, hang (await ()) until the Notempty signal is received, and the production put () requires a queue If it is full, hang (await ()) until the notfull signal is received.

There may be a problem, if a thread lock () object is suspended and there is no unlock, then another thread will not be able to get the lock (thelock () operation will hang), then it cannot be notified (notify the previous thread, wouldn't that be a "deadlock"?

await* operation

As mentioned in the previous section, multiple Reentrantlock are exclusive locks, and if a thread is not released after the lock is taken, then another thread must not be locked, so the Lock.lock () and Lock.unlock () There may be an action to release the lock (as well as an operation that acquires the lock once). We look back at the code, regardless of whether take () or put (), the only operation that can release the lock after entering Lock.lock ( ) is await () . That is, the await () operation actually releases the lock, then suspends the thread, wakes up once the condition is satisfied, and acquires the lock again!

Public final void await () throws Interruptedexception {
if (thread.interrupted ())
throw new Interruptedexception ();
Node node = Addconditionwaiter ();
int savedstate = fullyrelease (node);
int interruptmode = 0;
while (!isonsyncqueue (node)) {
Locksupport.park (this);
if ((Interruptmode = checkinterruptwhilewaiting (node))! = 0)
Break
}
if (acquirequeued (node, savedstate) && interruptmode! = Throw_ie)
Interruptmode = Reinterrupt;
if (node.nextwaiter! = null)
Unlinkcancelledwaiters ();
if (Interruptmode! = 0)
Reportinterruptafterwait (Interruptmode);
}

Above is the code snippet for await () . As mentioned in the previous section,AQS needs to have a CHL FIFO queue when acquiring a lock, so for a condition.await () , if you release the lock, you want to get the lock again then you need to go into the queue , waiting to be notified to acquire the lock. The complete await () operation is performed with the following steps:

      1. Joins the current thread to the Condition lock queue. In particular, this is different from the AQS queue, where the Condition FIFO queue is entered. This structure is discussed in detail later. Proceed to 2.
      2. Release the lock. You can see that the lock is released, or else the thread cannot get the lock and a deadlock occurs. Proceed to 3.
      3. Spin (while) hangs until awakened or timed out or cacelled. Proceed to 4.
      4. Gets the lock (acquirequeued). and release yourself from the Condition FIFO queue, indicating that you no longer need the lock (I've got the lock).

Here we look back at the data structure of Condition . We know that a Condition can be await* in multiple places (), then we need a FIFO structure to concatenate these Condition . Then wake up one or more (usually all) as needed. Therefore, a FIFO queue is required inside the Condition .

private transient Node firstwaiter;
private transient Node lastwaiter;

The two nodes above are the queues that describe a FIFO. We then combine the node data structure mentioned earlier. We found out that Node.nextwaiter was in handy! Nextwaiter is a series of condition.await* in series together to form a FIFO queue.

Signal/signalall operation

await* () is clear, it is much easier to see signal/signalall now. According to Signal/signalall , the first node in the FIFO queue in condition.await* () is awakened (or all node). Although all Node may be awakened, it is still only one thread that can get the lock, and the other threads that do not get the lock still need to spin the wait, which is the 4th step mentioned above (acquirequeued).

private void Dosignal (Node first) {
    do {
         if ((Firstwaiter = first.nextwaiter) = = null)
             lastwaiter = null;
        first.nextwaiter = null;
   } while (!transferforsignal (first) &&
              (first = firstwaiter)! = null);
}

private void Dosignalall (Node first) {
    lastwaiter = firstwaiter  = null;
    do {
        Node next = first.nextwaiter;
        first.nextwaiter = null;
        transferforsignal (first);
        first = next;
   } while (first! = NULL);
}

The above code is easy to see,signal is to wake up the first non-cancelled node thread in the Condition queue, and Signalall is to wake up all non-cancelled node threads. Of course, encountering a cancelled thread requires that it be removed from the FIFO queue.

Final Boolean Transferforsignal (node node) {
if (!compareandsetwaitstatus (node, node.condition, 0))
return false;

Node P = Enq (node);
int c = P.waitstatus;
if (C > 0 | |!compareandsetwaitstatus (P, C, node.signal))
Locksupport.unpark (Node.thread);
return true;
}

This is the process of waking up a await* () thread, as described in the previous section, if you want to Unpark the thread and get the thread to the lock, you need the thread node to enter the AQS queue. So you can see that the Enq (node) operation was called before Locksupport.unpark , adding the current node to the AQS queue.

The principle of the entire lock mechanism is introduced, from the next section to enter the application of the lock mechanism.

In layman Java Concurrency (9): Lock mechanism part 4[go]

Related Article

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.