Java concurrent series of AbstractQueuedSynchronizer source code analysis (exclusive mode ),

Source: Internet
Author: User

Java concurrent series of AbstractQueuedSynchronizer source code analysis (exclusive mode ),

In the previous article "Java concurrency series [1] ---- brief analysis of AbstractQueuedSynchronizer source code analysis", we introduced some basic concepts of AbstractQueuedSynchronizer, mainly focusing on how AQS queues are implemented, what is the exclusive mode and shared mode, and how to understand the node wait status. Understanding and understanding the content is the key to reading the AQS source code in the future. Therefore, it is easier for readers to read my previous article and look back at it. This article describes how a node enters the synchronization queue in the exclusive mode and what operations will be performed before it leaves the synchronization queue. AQS provides three methods for obtaining locks in exclusive mode and Shared Mode: Not responding to thread interruption acquisition, responding to thread interruption acquisition, and setting timeout time acquisition. The overall steps of these three methods are roughly the same, with only a few differences. Therefore, we understand that the implementation of other methods is similar. In this article, I will focus on obtaining methods that do not respond to thread interruptions. The other two methods will also introduce inconsistencies.

1. How to get the lock without responding to thread interruption?

// Obtain the public final void acquire (int arg) {if (! TryAcquire (arg) & acquireQueued (addWaiter (Node. EXCLUSIVE), arg) {selfInterrupt ();}}

Although the Code above looks simple, it performs the four steps shown in sequence. Next we will demonstrate the analysis step by step.

Step 1 :! TryAcquire (arg)

// Try to get the lock (exclusive mode) protected boolean tryAcquire (int arg) {throw new UnsupportedOperationException ();}

At this time, a person first tried to knock on the door. If he found that the door was not locked (tryAcquire (arg) = true), he went in directly. If the door is locked (tryAcquire (arg) = false), perform the next step. This tryAcquire method determines when the lock is open and when the lock is closed. This method must overwrite the subclass and overwrite the judgment logic.

Step 2: addWaiter (Node. EXCLUSIVE)

// Wrap the current Thread into a Node and add it to the end of the synchronization queue private Node addWaiter (Node mode) {// specify the lock-holding mode node Node = new Node (Thread. currentThread (), mode); // obtain the Node pred = tail referenced by the end Node of the synchronization queue; // if the end Node is not empty, it indicates that the synchronization queue has a Node if (pred! = Null) {// 1. point to the current tail node. prev = pred; // 2. set the current node to the end node if (compareAndSetTail (pred, node) {// 3. point the successor of the old end node to the new end node pred. next = node; return node; }}// otherwise, the synchronization queue has not initialized enq (node); return node ;} // The private Node enq (final Node node) {for (;) {// obtain the Node t = tail referenced by the end Node of the synchronization queue; // if the end Node is empty, the synchronization queue has not initialized if (t = null) {// initialize the synchronization queue if (compareAndSetHead (new Node ())) {tail = head ;}} else {// 1. point to the current tail node. prev = t; // 2. set the current node to the end node if (compareAndSetTail (t, node) {// 3. point the successor of the Old Tail node to the new tail node t. next = node; return t ;}}}}

When this step is performed, it indicates that the lock fails to be obtained for the first time. Then the person receives a block number card for himself and enters the queue area to queue, when receiving the number card, the system will declare the way in which you want to occupy the room (exclusive mode or shared mode ). Note: At this time, he does not sit down and rest (he hangs himself up.

Step 3: acquireQueued (addWaiter (Node. EXCLUSIVE), arg)

// Obtain the lock in an uninterrupted mode (exclusive mode) final boolean acquireQueued (final Node, int arg) {boolean failed = true; try {boolean interrupted = false; for (;) {// obtain the reference final Node p = node of the given Node. predecessor (); // if the current node is the first node of the synchronization queue, try to obtain the lock if (p = head & tryAcquire (arg )) {// set the given node as the head node setHead (node); // to help garbage collection, clear the subsequent parts of the previous head node p. next = null; // set the successful retrieval status failed = false; // return the interrupted status. The exit return interrupted is returned when the entire loop is executed here ;} // otherwise, it indicates that the lock status is still not available. Then, you can determine whether the current thread can be suspended. // if the result is true, the current thread will be suspended. Otherwise, the loop will continue, during this period, the thread does not respond to the interruption if (shouldParkAfterFailedAcquire (p, node) & parkAndCheckInterrupt () {interrupted = true ;}}} finally {// At the end, make sure that if the retrieval fails, the obtained if (failed) {cancelAcquire (node) ;}} will be canceled );}}} // determine whether the current Node can be suspended private static boolean shouldParkAfterFailedAcquire (Node pred, node Node) {// obtain the waiting state of the front-end Node int ws = pred. waitStatus; // if the status of the forward Node is SIGNAL, it indicates that the forward Node will wake up the current Node, so the current Node can safely suspend if (ws = Node. SIGNAL) {return true;} if (ws> 0) {// The following Operation clears all the canceled forward nodes in the synchronization queue do {node. prev = pred. prev;} while (pred. waitStatus> 0); pred. next = node;} else {// It indicates that the status of the forward node is not SIGNAL, and it is likely to be equal to 0, in this way, the forward Node will not wake up the current Node. // Therefore, the current Node must be in the SIGNAL status to safely suspend its own compareAndSetWaitStatus (pred, ws, Node. SIGNAL) ;}return false ;}// suspend the current thread private final boolean parkAndCheckInterrupt () {LockSupport. park (this); return Thread. interrupted ();}

This method is immediately executed after the number plate is obtained and enters the queuing area. There are two situations when a node enters the queuing area for the first time, one is to find that the person in front of him has left his seat and entered the room. Then he does not sit down and take a rest. He will try again to knock on the door to see if the boy is finished. If the person in it is just finished, he will not have to ask himself to rush in directly. Otherwise, you have to sit down and take a rest, but he is still not at ease. What if no one reminds him when he sits down and falls asleep? He left a small note on the seat of the person in front so that the person who came out of the paper could wake him up after seeing it. Another situation is that when he enters the queuing area and finds several other people queuing up in front of his seat, he can sit down and sit down for a while, but before that, he will leave a note in the seat of the person (who is asleep now) in front so that the person can wake himself up before leaving. After everything is done, he goes to bed with peace of mind. Note that we can see that the entire for loop has only one exit, that is, the thread can go out after it gets the lock successfully, the lock is always attached to the parkAndCheckInterrupt () method of the for loop before it is obtained. After the thread is awakened, it also continues to execute the for loop from this place.

Step 4: selfInterrupt ()

// The current Thread will interrupt private static void selfInterrupt () {Thread. currentThread (). interrupt ();}

Since the entire thread has been hanging in the parkAndCheckInterrupt () method of the for Loop, no response to any form of thread interruption is returned before the lock is successfully obtained, only after the thread successfully acquires the lock and loops out from the for Loop will it check whether there are any requests to interrupt the thread during this period. If yes, it will call selfInterrupt () again () the method suspends itself.

2. How to get the lock in response to thread interruption?

// Obtain the lock (exclusive) private void doAcquireInterruptibly (int arg) in the break mode) throws InterruptedException {// wrap the current thread into a Node and add it to the synchronization queue. final node Node = addWaiter (Node. EXCLUSIVE); boolean failed = true; try {for (;) {// obtain the final Node p = node of the current Node. predecessor (); // if p is the head node, the current thread attempts to obtain the lock again if (p = head & tryAcquire (arg) {setHead (node ); p. next = null; // help GC failed = false; // return after the lock is obtained successfully;} // if conditions are met, the current thread is suspended, at this time, the response is interrupted and an exception is thrown if (shouldParkAfterFailedAcquire (p, node) & parkAndCheckInterrupt ()) {// if an interrupted request is found after the thread is awakened, an exception throw new InterruptedException () ;}} finally {if (failed) {cancelAcquire (node) ;}} will be thrown );}}}

The response thread interrupt method is the same as the non-response thread interrupt method to obtain the lock process. The only difference is that when a thread wakes up from the parkAndCheckInterrupt method, it checks whether the thread is interrupted. If yes, it throws an InterruptedException exception, the method for obtaining a lock without responding to thread interruption is to set the interrupt status after receiving the interrupt request, and does not immediately end the method for obtaining the lock, the node determines whether to suspend itself based on the interruption status until the node has successfully obtained the lock.

3. How to set the timeout time to get the lock?

// Get the lock (exclusive mode) private boolean doAcquireNanos (int arg, long nanosTimeout) throws InterruptedException {// obtain the current System time long lastTime = System. nanoTime (); // wrap the current thread as a Node and add it to the synchronization queue. final node Node = addWaiter (Node. EXCLUSIVE); boolean failed = true; try {for (;) {// obtain the final Node p = node of the current Node. predecessor (); // if the forward step is a head node, the current thread attempts to obtain the lock again if (p = head & tryAcquire (arg )) {// update head node setHead (node); p. next = null; failed = false; return true;} // exit the loop if (nanosTimeout <= 0) {return false ;} // if the timeout value is greater than the spin time, the thread will be suspended for a period of time after the thread can be suspended. if (shouldParkAfterFailedAcquire (p, node) & nanosTimeout> spinForTimeoutThreshold) {// suspend the current thread for a period of time, and then wake up LockSupport. parkNanos (this, nanosTimeout);} // obtain the current System time long now = System. nanoTime (); // The time-out time minus the time interval for obtaining the lock. nanosTimeout-= now-lastTime; // update lastTime again. lastTime = now; // if (Thread. interrupted () {throw new InterruptedException () ;}} finally {if (failed) {cancelAcquire (node );}}}

When the timeout value is set, the lock is obtained first. When the first attempt fails to obtain the lock, the thread will be suspended for a period of time if the input timeout value is greater than the spin time, otherwise, the system will spin and the time-out time minus the time used to obtain the lock. If the time-out period is less than 0, the time-out period is used up. In this case, the lock acquisition operation is terminated and a failure mark is returned. Note that the thread interrupt request can be returned when the lock is obtained during the timeout period.

4. How does a thread release the lock and exit the synchronization queue?

// Unlock (exclusive mode) public final boolean release (int arg) {// call the lock to see if the lock can be unlocked if (tryRelease (arg )) {// get head Node h = head; // if the head Node is not empty and the waiting status is not equal to 0, wake up the next Node if (h! = Null & h. waitStatus! = 0) {// wake up the next Node unparkSuccessor (h);} return true;} return false;} // wake up the next node private void unparkSuccessor (Node node) {// get the waiting state of a given node int ws = node. waitStatus; // update the wait status to 0 if (ws <0) {compareAndSetWaitStatus (node, ws, 0);} // obtain the Node s = node of the successor node of the given Node. next; // The successor node is null or the wait state is canceled if (s = null | s. waitStatus> 0) {s = null; // retrieve the first Node that is not in the canceled state from the forward traversal Queue (Node t = tail; t! = Null & t! = Node; t = t. prev) {if (t. waitStatus <= 0) {s = t ;}} // wake up the first node behind a given node if (s! = Null) {LockSupport. unpark (s. thread );}}

After the thread holds the lock and enters the room, it will do its own thing. After the lock is completed, it will release the lock and leave the room. The tryRelease method can be used to unlock the password. We know that the tryRelease method requires sub-classes to be overwritten. Different sub-classes have different implementation rules, that is, different sub-classes have different passwords. For example, in ReentrantLock, every time the people in the room call the tryRelease method, the state will be reduced by 1 until the password lock is enabled when the state is reduced to 0. We think that this process is not like we are constantly rotating the wheel of the password lock, and the number of the wheel is reduced by 1 each time. CountDownLatch is a bit similar to this one, but it is not a one-person turn, but many people turn around, concentrate on the power of everyone to unlock. When the thread leaves the room, it will find its original seat, that is, the head node. Check whether someone left a small note on the seat. If so, it knows that someone is asleep and needs to wake it up. Then it will wake up the thread. If not, it indicates that no one in the synchronization queue is waiting for the moment, and no one needs it to wake up, so it can leave with peace of mind. The above process is the process of releasing the lock in the exclusive mode.

Note: All the above analyses are based on JDK1.7, which may vary with different versions.

The above is all the content of this article. I hope it will be helpful for your learning and support for helping customers.

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.