Java concurrency series [2] ---- exclusive mode of AbstractQueuedSynchronizer source code analysis,
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?
1 // do not respond to the interruption Method acquisition (exclusive mode) 2 public final void acquire (int arg) {3 if (! TryAcquire (arg) & acquireQueued (addWaiter (Node. EXCLUSIVE), arg) {4 selfInterrupt (); 5} 6}
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)
1 // try to get the lock (exclusive mode) 2 protected boolean tryAcquire (int arg) {3 throw new UnsupportedOperationException (); 4}
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)
1 // wrap the current thread into a Node and add it to the end of the synchronization queue. 2 private Node addWaiter (Node mode) {3 // specify the lock mode. 4 Node node Node = new Node (Thread. currentThread (), mode); 5 // get the synchronization queue End Node reference 6 Node pred = tail; 7 // if the end Node is not empty, indicates that the synchronization queue already has node 8 if (pred! = Null) {9 // 1. point to the current end node 10 node. prev = pred; 11 // 2. set the current node to the end node 12 if (compareAndSetTail (pred, node) {13 // 3. point the successor of the old end node to the new end node 14 pred. next = node; 15 return node; 16} 17} 18 // otherwise, the synchronization queue has not initialized 19 enq (node); 20 return node; 21} 22 23 // Node team-up operation 24 private Node enq (final node Node) {25 (;;) {26 // get the end Node of the synchronization queue reference 27 Node t = tail; 28 // if the end Node is empty, the synchronization queue has not initialized 29 if (t = null) {30 // initialize synchronization queue 31 if (compareAndSetHead (new Node () {32 tail = head; 33} 34} else {35 // 1. point to the current end node 36 node. prev = t; 37 // 2. set the current node to the End node 38 if (compareAndSetTail (t, node) {39 // 3. point the successor of the Old Tail node to the new tail node 40 TB. next = node; 41 return t; 42} 43} 44} 45}
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)
1 // obtain the lock in an uninterrupted mode (exclusive mode) 2 final boolean acquireQueued (final Node, int arg) {3 boolean failed = true; 4 try {5 boolean interrupted = false; 6 for (;) {7 // obtain the reference of the forward Node of the given node 8 final Node p = node. predecessor (); 9 // if the current node is the first node of the synchronization queue, try to get the lock 10 if (p = head & tryAcquire (arg )) {11 // set the given node to head node 12 setHead (node); 13 // to help garbage collection, clear the next of the previous head node 14 p. next = null; 15 // set the status of successful retrieval: 16 failed = false; 17 // return the interrupted status. Exit 18 return interrupted when the entire loop is executed here; 19} 20 // otherwise, it indicates that the lock status is still not available. In this case, determine whether the current thread can be suspended. 21 // 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 22 if (shouldParkAfterFailedAcquire (p, node) & parkAndCheckInterrupt () {23 interrupted = true; 24} 25} 26} finally {27 // At the end, ensure that if the retrieval fails, cancel obtaining 28 if (failed) {29 cancelAcquire (node ); 30} 31} 32} 33 34 // determines whether the current Node can be suspended 35 private static boolean shouldParkAfterFailedAcquire (Node pred, node Node) {36 // get the waiting status of the forward node 37 int ws = pred. waitStatus; 38 // if the status of the frontend Node is SIGNAL, it indicates that the frontend Node will wake up the current Node, so the current Node can safely suspend 39 if (ws = Node. SIGNAL) {40 return true; 41} 42 43 if (ws> 0) {44 // The following operation is to clear all canceled forward nodes in the synchronization queue 45 do {46 node. prev = pred. prev; 47} while (pred. waitStatus> 0); 48 pred. next = node; 49} else {50 // here it indicates that the status of the forward node is not SIGNAL, and it is likely to be equal to 0, in this case, the current Node will not wake up the current Node 51 // so the current Node must ensure that the status of the current Node is SIGNAL in order to safely suspend itself 52 compareAndSetWaitStatus (pred, ws, Node. SIGNAL); 53} 54 return false; 55} 56 57 // suspend the current thread 58 private final boolean parkAndCheckInterrupt () {59 LockSupport. park (this); 60 return Thread. interrupted (); 61}
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 ()
1 // The current Thread will interrupt itself 2 private static void selfInterrupt () {3 Thread. currentThread (). interrupt (); 4}
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?
1 // obtain the lock in an exclusive mode. 2 private void doAcquireInterruptibly (int arg) throws InterruptedException {3 // wrap the current thread into a Node and add it to the synchronization queue. 4 final node Node = addWaiter (Node. EXCLUSIVE); 5 boolean failed = true; 6 try {7 for (;) {8 // get the front Node of the current node 9 final Node p = node. predecessor (); 10 // if p is the head node, the current thread will try again to obtain the lock 11 if (p = head & tryAcquire (arg )) {12 setHead (node); 13 p. next = null; // help GC14 failed = false; 15 // return 16 return after the lock is obtained successfully; 17} 18 // if conditions are met, the current thread is suspended, at this time, the response is interrupted and an exception is thrown 19 if (shouldParkAfterFailedAcquire (p, node) & parkAndCheckInterrupt ()) {20 // if an interrupted request is found after the thread is awakened, an exception is thrown. 21 throw new InterruptedException (); 22} 23} 24} finally {25 if (failed) {26 cancelAcquire (node); 27} 28} 29}
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?
1 // get the lock (exclusive mode) with a limited timeout value 2 private boolean doAcquireNanos (int arg, long nanosTimeout) throws InterruptedException {3 // get the current System time 4 long lastTime = System. nanoTime (); 5 // wrap the current thread as a Node and add it to the synchronization queue. 6 final node Node = addWaiter (Node. EXCLUSIVE); 7 boolean failed = true; 8 try {9 for (;) {10 // get the front Node of the current node 11 final Node p = node. predecessor (); 12 // if the forward step is a head node, the current thread attempts to obtain the lock again 13 if (p = head & tryAcquire (arg )) {14 // update head node 15 setHead (node); 16 p. next = null; 17 failed = false; 18 return true; 19} 20 // exit loop 21 if (nanosTimeout <= 0) {22 return false when the timeout time is used up; 23} 24 // if the timeout value is greater than the spin time, the thread will be suspended for a period of 25 if (shouldParkAfterFailedAcquire (p, node) after the thread can be suspended) & amp; nanosTimeout & gt; spinForTimeoutThreshold) {26 // suspend the current thread for a period of time, and then wake up with 27 LockSupport. parkNanos (this, nanosTimeout); 28} 29 // obtain the current System time 30 long now = System. nanoTime (); 31 // time-out time minus the time interval for obtaining the lock 32 nanosTimeout-= now-lastTime; 33 // update lastTime34 lastTime = now again; 35 // an exception 36 if (Thread. interrupted () {37 throw new InterruptedException (); 38} 39} 40} finally {41 if (failed) {42 cancelAcquire (node); 43} 44} 45}
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?
1 // unlock operation (exclusive mode) 2 public final boolean release (int arg) {3 // trigger the password lock to see if the lock can be unlocked 4 if (tryRelease (arg )) {5 // get head Node 6 Node h = head; 7 // if the head Node is not empty and the waiting status is not equal to 0, wake up the next Node 8 if (h! = Null & h. waitStatus! = 0) {9 // wake up the next node 10 unparkSuccessor (h); 11} 12 return true; 13} 14 return false; 15} 16 17 // wake up the successor Node 18 private void unparkSuccessor (node) {19 // get the waiting status of the given Node 20 int ws = node. waitStatus; 21 // update the wait status to 022 if (ws <0) {23 compareAndSetWaitStatus (node, ws, 0 ); 24} 25 // obtain the successor Node of a given node 26 Node s = node. next; 27 // The successor node is null or the wait state is canceled 28 if (s = null | s. waitStatus> 0) {29 s = null; 30 // traverse the queue from the back to find the first node 31 fo that is not in the canceled status R (Node t = tail; t! = Null & t! = Node; t = t. prev) {32 if (t. waitStatus <= 0) {33 s = t; 34} 35} 36} 37 // wake up the first node after a given node that is not in the canceled status 38 if (s! = Null) {39 LockSupport. unpark (s. thread); 40} 41}
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. Different versions may differ.