Back to the home page
The article is long and informative, so it is recommended to read it on PC. Article title is to echo the previous, in fact, can be written separately, mainly hope that readers can see the article system.
This article focuses on the following points:
- Understand the difference between fair and unfair ReentrantLock
- In-depth analysis of AbstractQueuedSynchronizer ConditionObject
- Learn more about Java thread interrupts and InterruptedException
Basically clarify above points are presented in this paper, I assume that the reader read an article in the introduction to AbstractQueuedSynchronizer, of course, if you are already familiar with the exclusive lock in AQS, that also can see this article directly. There is basically no relationship between sections, so you can just focus on what you are interested in.
- Fair locks and unfair locks
- Condition
- 1. Add the node to the conditional queue
- 2. Release the exclusive lock completely
- 3. Wait to enter the blocking queue
- 4. Signal wakes up the thread and moves to the blocking queue
- 5. Check the interruption status after waking up
- Obtain an exclusive lock
- 7. Handle the interruption status
- * await with timeout mechanism
- * Do not throw InterruptedException await
- The cancellation of the AbstractQueuedSynchronizer exclusive lock queue
- InterruptedException InterruptedException
- Thread the interrupt
- InterruptedException overview
- Handling interrupts
- conclusion
Fair locks and unfair locks
ReentrantLock defaults to unfair locking unless you pass true in the constructor.
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
Copy the code
Lock method for fair locking:
static final class FairSync extends Sync { final void lock() { acquire(1); } // AbstractQueuedSynchronizer.acquire(int arg) public final void acquire(int arg) { if (! tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); If (c == 0) {// 1. If (c == 0) {// 1. hasQueuedPredecessors() && 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; }}Copy the code
The lock method for an unfair lock:
static final class NonfairSync extends Sync { final void lock() { // 2. If (compareAndSetState(0, 1)) setExclusiveOwnerThread(thread.currentThread ())); else acquire(1); } // AbstractQueuedSynchronizer.acquire(int arg) public final void acquire(int arg) { if (! tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } } /** * Performs non-fair tryLock. tryAcquire is implemented in * subclasses, but both need nonfair try for trylock method. */ final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }Copy the code
Summary: There are only two differences between fair and unfair locks:
- When an unfair lock is called, CAS will be called to grab the lock. If the lock is not occupied at this time, CAS will return the lock.
- After a CAS failure, an unfair lock will enter the tryAcquire method just like a fair lock. In the tryAcquire method, if the lock is released (state == 0), the unjust lock will be directly seized by CAS. However, fair lock will determine whether there is a thread waiting in the queue, if there is, it will not grab the lock, obediently queue to the back.
There are only two differences between a fair lock and an unfair lock. If both CAS are unsuccessful, then the unfair lock is the same as the fair lock.
Relatively speaking, unfair locks have better performance because of their higher throughput. Of course, unfair locking makes the timing of lock acquisition more uncertain and can lead to chronically hungry threads in the blocking queue.
Condition
Tips: here again, want to understand this, must be to read an article about AbstractQueuedSynchronizer introduction, or you already have the related knowledge, this section must be look not to understand.
Let’s take a look at the Condition usage scenario. Condition is often used in producer-consumer scenarios. See Doug Lea for an example:
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; class BoundedBuffer { final Lock lock = new ReentrantLock(); // Condition depends on lock to produce final condition notFull = lock.newcondition (); final Condition notEmpty = lock.newCondition(); final Object[] items = new Object[100]; int putptr, takeptr, count; Public void put(Object x) throws InterruptedException {lock.lock(); try { while (count == items.length) notFull.await(); // Queue is full, wait until not full to continue production items[putptr] = x; if (++putptr == items.length) putptr = 0; ++count; notEmpty.signal(); } finally {lock.unlock(); Throw InterruptedException {lock.lock(); try { while (count == 0) notEmpty.await(); Object x = items[takeptr]; if (++takeptr == items.length) takeptr = 0; --count; notFull.signal(); Return x; return x; } finally { lock.unlock(); }}}Copy the code
(ArrayBlockingQueue implements producer-consumer in this way, so please use this example only as a learning example. You can use ArrayBlockingQueue directly in production.)
We often use obj.wait(), obj.notify(), or obj.Notifyall () to implement similar functions, but they are based on object monitor locks. Those who need a more in-depth look at these methods should refer to my other article, Threads and Locks: An In-depth Look at the Java 8 Programming language specification. While the Condition is based on the already in here, and already is dependent on AbstractQueuedSynchronizer.
Before moving on, the reader should have a general idea in mind. Condition is dependent on ReentrantLock and must acquire the lock in order to operate either with an await call or with a signal wake up call.
Each ReentrantLock instance can produce multiple ConditionObject instances by calling newCondition multiple times:
final ConditionObject newCondition() {
return new ConditionObject();
}
Copy the code
We first look at we focus on the Condition of the implementation of the class AbstractQueuedSynchronizer ConditionObject in class.
public class ConditionObject implements Condition, java.io.Serializable { private static final long serialVersionUID = 1173984872572414699L; Private transient Node firstWaiter; private transient Node firstWaiter; Private TRANSIENT Node lastWaiter; .Copy the code
When WE introduced AQS last time, we had a blocking queue that held the queue of threads waiting to acquire locks. So let’s introduce another concept here, called the condition queue, and I’ve drawn a simple diagram to illustrate this.
The blocking queue here would be better called sync queue, but I’ll stick with it for the sake of the previous article. Remember the two concepts here, blocking queues and conditional queues.
Here, let’s briefly review Node’s properties:
volatile int waitStatus; CANCELLED(1), SIGNAL(-1), CONDITION(-2), PROPAGATE(-3); volatile Node next; volatile Thread thread; Node nextWaiter;Copy the code
Prev and Next are used to implement a bidirectional list of blocking queues, and nextWaiter is used to implement a unidirectional list of conditional queues
Basically, if you understand this diagram, you will know how condition is processed. So, I’ll explain the diagram briefly and then explain the code implementation in detail.
- We know that an instance of ReentrantLock can generate multiple Condition instances by calling newCondition() multiple times, which corresponds to condition1 and condition2. Notice that ConditionObject has only two attributes: firstWaiter and lastWaiter;
- Each condition has an associated condition queue. If thread 1 calls condition1.await(), it will wrap the current thread 1 as Node and join the condition queue. The condition queue is a one-way linked list.
- Condition1.signal () moves condition1’s firstWaiter to the end of the blocking queue, waits to obtain the lock, returns the await method and continues.
I’m talking about 1, 2, 3 here as the simplest flow, leaving out interrupts, signalAll, and await methods with timeout parameters, etc., but figuring that out is the main purpose of this section.
At the same time, it is very intuitive to see which operations are thread-safe and which are not.
Once you understand this diagram, the following code analysis should be simple.
Let’s take a look at the wait method:
AwaitUninterruptibly () this method blocks until signal(signal() and signalAll() are called, Public final void await() throws InterruptedException {if (thread.interrupted ()) throw new InterruptedException(); // add to the condition queue Node Node = addConditionWaiter(); Int savedState = fullyRelease(node); int interruptMode = 0; / / here to exit the loop there are two kinds of circumstances, after careful analysis / / 1. IsOnSyncQueue (node) returns true, the current node has been transferred to the blocking queue / / 2. CheckInterruptWhileWaiting (node)! = 0 to break, then exit the loop, representing the thread interrupt 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) // clean up if cancelled unlinkCancelledWaiters(); if (interruptMode ! = 0) reportInterruptAfterWait(interruptMode); }Copy the code
In fact, I have generally said the whole await process, let’s step by step to the above several points with the source code.
1. Add the node to the conditional queue
AddConditionWaiter () adds the current node to the conditional queue, which is thread-safe.
Private Node addConditionWaiter() {Node t = lastWaiter; // If the last node of the conditional queue is cancelled, it is cleared. = null && t.waitStatus ! UnlinkCancelledWaiters () = node.condition) {// This method works through the CONDITION queue and clears the cancelled nodes from the queue; t = lastWaiter; } Node node = new Node(Thread.currentThread(), Node.CONDITION); // If (t == null) firstWaiter = node; else t.nextWaiter = node; lastWaiter = node; return node; }Copy the code
In the addWaiter method, there is an unlinkCancelledWaiters() method, which is used to clear nodes from the queue that have been unwaited for.
This method is called once if a cancel operation occurs while await (this will be said later), or if the last node is found to be cancelled while the node is being queued.
// Wait queue is a one-way linked list, traversing the list to remove the node that has canceled the wait. Private void unlinkCancelledWaiters() {Node t = firstWaiter; Node trail = null; while (t ! = null) { Node next = t.nextWaiter; // If (t.waitStatus!) the Node is cancelled if (t.waitStatus! = Node.CONDITION) { t.nextWaiter = null; if (trail == null) firstWaiter = next; else trail.nextWaiter = next; if (next == null) lastWaiter = trail; } else trail = t; t = next; }}Copy the code
2. Release the exclusive lock completely
After the node is queued, int savedState = fullyRelease(node) is called; Method to release the lock. Note that the exclusive lock is fully released because ReentrantLock is reentrant.
// For the simplest operation: lock. Lock (), condition1.await(). // savedState == n if the lock is reentered n times // If this method fails, the node is set to "cancel", And throw an exception IllegalMonitorStateException final int fullyRelease (Node Node) {Boolean failed = true; try { int savedState = getState(); If (release(savedState)) {failed = false; return savedState; } else { throw new IllegalMonitorStateException(); } } finally { if (failed) node.waitStatus = Node.CANCELLED; }}Copy the code
3. Wait to enter the blocking queue
So once you release the lock, then you have this, and then you spin, and if you find that you’re not in the blocking queue, then you hang up and wait to be transferred to the blocking queue.
int interruptMode = 0; while (! IsOnSyncQueue (node)) {// Thread suspens locksupport. park(this); if ((interruptMode = checkInterruptWhileWaiting(node)) ! = 0) break; }Copy the code
IsOnSyncQueue (Node Node) is used to determine whether the Node has been moved to the blocking queue:
CONDITION = node. CONDITION; // Signal is queued from a CONDITION to a blocking queue. Final Boolean isOnSyncQueue(node node) {// The node waitStatus is set to 0, // If waitStatus is still node. CONDITION (-2), it must be in the CONDITION queue. // If the Node prev is still null, There is in a blocking queue if (node. WaitStatus = = node. CONDITION | | node. The prev = = null) return false. // If node has a successor node next, it must be blocking the queue. = null) return true; Node.prev () = node.prev(); node.prev() = node.prev() = null to infer that node is blocking on the queue? The answer is: no. // Set node.prev to tail, and then CAS to set itself to the new tail, but this time the CAS may fail. Return findNodeFromTail(node); findNodeFromTail(node); findNodeFromTail(node); } // Return true private Boolean findNodeFromTail(Node Node) {Node t = tail; for (;;) { if (t == node) return true; if (t == null) return false; t = t.prev; }}Copy the code
Returning to the previous loop, isOnSyncQueue(node) returns false, then locksupport.park (this); Here the thread hangs.
4. Signal wakes up the thread and moves to the blocking queue
For your understanding, here we look at the wake up operation first, because we just went to locksupport.park (this); The thread is suspended, waiting to wake up.
The wake up operation is usually performed by another thread, as in the producer-consumer pattern, if the thread is suspended for consumption, then when the producer produces something, it calls signal to wake up the waiting thread to consume it.
Public final void signal() {// The thread calling signal must hold the current exclusive lock if (! isHeldExclusively()) throw new IllegalMonitorStateException(); Node first = firstWaiter; if (first ! = null) doSignal(first); } // Go back through the conditional queue to find the first node that needs to be transferred // Because we said earlier that some threads cancel the queue, Private void doSignal(Node first) {do {// Open the firstWaiter to the first Node after removing the queue header. If ((firstWaiter = first.nextwaiter) == null) lastWaiter = null; First.nextwaiter = null; first.nextwaiter = null; } while (! transferForSignal(first) && (first = firstWaiter) ! = null); If first fails, select the first node after first to do so, and so on} // Move the node from the conditional queue to the blocking queue // true for successful transfer // false for before signal, Final Boolean transferForSignal(Node Node) {// CAS fails if waitStatus of this Node is not Node.CONDITION, the Node has been cancelled. // If waitStatus is set to 0 if (! compareAndSetWaitStatus(node, Node.CONDITION, 0)) return false; // enq(node): spins into the end of the blocking queue // Note that the return value p is node in the front of the blocking queue. int ws = p.waitStatus; // ws > 0 indicates that node unlocks the wait lock on the precursor node in the blocking queue, and directly wakes up the corresponding node thread. // If ws <= 0, compareAndSetWaitStatus will be called. Need to set the state of the precursor Node to Node. The SIGNAL (1) if (ws > 0 | |! CompareAndSetWaitStatus (p, ws, node.signal)) // Locksupport.unpark (node.thread); return true; }Copy the code
Under normal circumstances, the ws > 0 | |! CompareAndSetWaitStatus (p, ws, Node.SIGNAL) ws <= 0 and compareAndSetWaitStatus(p, ws, Node.SIGNAL) returns true. So you don’t wake up the node thread in the if block. This method then returns true, which means that the signal method ends and the node enters the blocking queue.
If a blocking occurs, the precursor node in the queue cancels the wait, or the CAS fails, simply wake up the thread and let it proceed to the next step.
5. Check the interruption status after waking up
After signal in the previous step, our thread is moved from the conditional queue to the blocking queue, and is ready to acquire the lock. Once the lock is retrieved, continue to execute.
Wait for the thread to recover from suspension and continue reading
int interruptMode = 0; while (! IsOnSyncQueue (node)) {// Thread suspens locksupport. park(this); if ((interruptMode = checkInterruptWhileWaiting(node)) ! = 0) break; }Copy the code
InterruptMode is explained first. InterruptMode can be REINTERRUPT (1), THROW_IE (-1), or 0
- REINTERRUPT: When an await is returned, the interrupt status needs to be reset
- THROW_IE: To return an await, InterruptedException is thrown
- 0: indicates that no interrupt has occurred during await
There are three situations that will make locksupport.park (this); This return continues:
- Normal road strength. Signal -> Transfer node to blocking queue -> Lock acquired (unpark)
- The thread is interrupted. While in park, another thread interrupts this thread
- Signal when we said that the precursor node after the transfer was cancelled or the CAS operation to the precursor node failed
- Wake up. Object.wait() also has this problem
Thread wakes up after the first step is to call checkInterruptWhileWaiting (node) this method, this method is used to determine whether interruption happened during thread hanging, if interrupt comes up is interrupted before signal invocation, or signal after the interrupt.
If the signal has been interrupted before, return THROW_IE // 2. If interrupt is after signal, return REINTERRUPT // 3. There was no interruption, Return 0 private int checkInterruptWhileWaiting (Node Node) {return Thread. Interrupted ()? (transferAfterCancelledWait (Node) ? THROW_IE : REINTERRUPT) : 0; }Copy the code
Thread.interrupted() : If the current Thread is interrupted, this method returns true and resets the interrupted status to false, hence the use of reinterrupts.
See how to determine whether the interrupt occurred before or after signal:
// This method is called only when the thread is interrupted. // If necessary, the node whose wait has been canceled is moved to the blocking queue. // Returns true: If this thread before the signal is cancelled, final Boolean transferAfterCancelledWait (Node to Node) {/ / the Node status is set to 0 with CAS / / step if the CAS is successful, The interrupt occurred before signal, because if signal had occurred first, If (compareAndSetWaitStatus(node, node.condition, 0)) {// Put the node on a blocking queue. The blocking queue enq(node) will still be moved; return true; } // This is because the CAS failed, it must be because the signal method has set waitStatus to 0. // The signal method will transfer the node to the blocking queue, but it is not finished yet, so the spin waits for it to complete. After the signal call, an interrupt occurs while (! isOnSyncQueue(node)) Thread.yield(); return false; }Copy the code
Again, even if an interrupt occurs, the node is still moved to the blocking queue.
At this point, you probably know how the while loop exits. Either an interruption or a successful transfer.
Obtain an exclusive lock
After the while loop comes out, here’s the code:
if (acquireQueued(node, savedState) && interruptMode ! = THROW_IE) interruptMode = REINTERRUPT;Copy the code
Since the while comes out, we determine that the node has entered the blocking queue and is ready to acquire the lock.
AcquireQueued (node, savedState) acquireQueued(node, savedState) acquireQueued(node, savedState) acquireQueued(node, savedState) This means that the current thread has acquired the lock, and state == savedState.
Note that the blocking queue is entered regardless of whether an interrupt has occurred, and the return value of the acquireQueued(Node, savedState) is to indicate whether the thread has been interrupted. If true is returned, it is interruptMode! = THROW_IE indicates that interrupts occurred before signal. Set interruptMode to REINTERRUPT for future interrupts.
Moving on:
if (node.nextWaiter ! = null) // clean up if cancelled unlinkCancelledWaiters(); if (interruptMode ! = 0) reportInterruptAfterWait(interruptMode);Copy the code
In the spirit of being meticulous, here’s Node.nextwaiter! = null how to satisfy. NextWaiter = null to disconnect the node from the conditional queue.
However, in the case of determining an interrupt, did it occur before or after signal? NextWaiter = null Node.nextwaiter = null Node.nextwaiter = null node.nextwaiter = null node.nextwaiter = null
As we said earlier, the unlinkCancelledWaiters method is also called when a node cancels, so here it is.
7. Handle the interruption status
At this point, we can finally say what interruptMode is used for.
- 0: Do nothing.
- The THROW_IE: await method throws InterruptedException
- REINTERRUPT: Reinterrupts the current thread
private void reportInterruptAfterWait(int interruptMode)
throws InterruptedException {
if (interruptMode == THROW_IE)
throw new InterruptedException();
else if (interruptMode == REINTERRUPT)
selfInterrupt();
}
Copy the code
Why do you do that? This part of knowledge is in the last section of this article
* await with timeout mechanism
ConditionObject is completely parsed after the previous 7 steps. Let’s briefly analyze the await method with the timeout mechanism.
public final long awaitNanos(long nanosTimeout)
throws InterruptedException
public final boolean awaitUntil(Date deadline)
throws InterruptedException
public final boolean await(long time, TimeUnit unit)
throws InterruptedException
Copy the code
All three methods are similar, so let’s pick one out:
public final boolean await(long time, TimeUnit Unit) throws InterruptedException {// Long nanosTimeout = unit.tonanos (time); if (Thread.interrupted()) throw new InterruptedException(); Node node = addConditionWaiter(); int savedState = fullyRelease(node); Final Long deadline = system.nanotime () + nanosTimeout; Boolean timedOut = false; int interruptMode = 0; while (! IsOnSyncQueue (node)) {if (nanosTimeout <= 0L) { Cancel your waiting must call transferAfterCancelledWait (node) / / this method returns true if the method, in this method, moving nodes to blocking queue success / / returns false, the signal has occurred, The signal method transfers the node. That is no timeout, timedout = transferAfterCancelledWait (node); break; } // The value of spinForTimeoutThreshold is 1000 nanoseconds, that is, 1 millisecond // that is, if less than 1 millisecond, do not select parkNanos, If (nanosTimeout >= spinForTimeoutThreshold) locksupport. parkNanos(this, nanosTimeout); if ((interruptMode = checkInterruptWhileWaiting(node)) ! = 0) break; NanosTimeout = deadline-system.nanotime (); } if (acquireQueued(node, savedState) && interruptMode ! = THROW_IE) interruptMode = REINTERRUPT; if (node.nextWaiter ! = null) unlinkCancelledWaiters(); if (interruptMode ! = 0) reportInterruptAfterWait(interruptMode); return ! timedout; }Copy the code
The idea of timeout is very simple. ‘await’ with no timeout parameter is park and wait for someone to wake up. Now it is time to call the parkNanos method to sleep for the specified time, wake up and determine if signal has been called, if it has not timed out, otherwise it has timed out. If you time out, do it yourself, move to the blocking queue, and grab the lock.
* Do not throw InterruptedException await
The last section of Condition.
public final void awaitUninterruptibly() { Node node = addConditionWaiter(); int savedState = fullyRelease(node); boolean interrupted = false; while (! isOnSyncQueue(node)) { LockSupport.park(this); if (Thread.interrupted()) interrupted = true; } if (acquireQueued(node, savedState) || interrupted) selfInterrupt(); }Copy the code
It’s simple. I’ll cut the crap.
The cancellation of the AbstractQueuedSynchronizer exclusive lock queue
This article said is AbstractQueuedSynchronizer, just like Condition said too much, to get ideas.
Next, I want to talk about how to eliminate competition for locks.
As mentioned in the previous article, the most important method is this, which is where we will find the answer:
final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; for (;;) { final Node p = node.predecessor(); if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC failed = false; return interrupted; } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); }}Copy the code
First, the node must have been enqueued by this method.
I posted the parkAndCheckInterrupt() code:
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
Copy the code
If you put these two pieces of code together, it’s clear.
If we want to unqueue one thread, we need to interrupt it in another thread. For example, if a thread calls lock() for a long time and does not return, I want to interrupt it. Once it is interrupted, the thread is removed from locksupport.park (this); Wake up, then thread.interrupted (); Returns true.
One problem we found was that even if the interrupt woke up the thread, it simply set interrupted = true and continued the next loop. Also, because thread.interrupted (); Clears the interrupt state and returns false the second time parkAndCheckInterrupt is entered.
So, see that in this method the interrupted is used only to record whether or not an interruption has occurred, and then used for the method to return the value, and does nothing else.
So let’s see how the outer method handles the acquireQueued return false.
public final void acquire(int arg) { if (! tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } static void selfInterrupt() { Thread.currentThread().interrupt(); }Copy the code
So the lock() method handles interrupts as if you interrupt and I grab the lock anyway. It doesn’t really matter, except that after I grab the lock, I set the thread’s interrupt state without throwing any exceptions. Once the lock is acquired, the caller can either check to see if the interrupt occurred or ignore it.
Let me draw a dividing line. Did you feel cheated? I said a lot, but it had nothing to do with cancellation.
Let’s look at another lock method for ReentrantLock:
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
Copy the code
Throws InterruptedException (InterruptedException).
public final void acquireInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); if (! tryAcquire(arg)) doAcquireInterruptibly(arg); }Copy the code
Keep going:
private void doAcquireInterruptibly(int arg) throws InterruptedException { final Node node = addWaiter(Node.EXCLUSIVE); boolean failed = true; try { for (;;) { final Node p = node.predecessor(); if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC failed = false; return; } the if (shouldParkAfterFailedAcquire (p, node) && parkAndCheckInterrupt ()) / / is here, once the exception, immediately put an end to this method, an exception is thrown. LockInterruptibly throw new InterruptedException(); lockInterruptibly throw new InterruptedException(); If (failed) cancelAcquire(node); if (interrupted dexception) cancelAcquire(node); }}Copy the code
While we’re at it, let’s talk about cancelAcquire:
private void cancelAcquire(Node node) { // Ignore if node doesn't exist if (node == null) return; node.thread = null; // Skip cancelled predecessors Node pred = node.prev; while (pred.waitStatus > 0) node.prev = pred = pred.prev; // predNext is the apparent node to unsplice. CASes below will // fail if not, in which case, we lost race vs another cancel // or signal, so no further action is necessary. Node predNext = pred.next; // Can use unconditional write instead of CAS here. // After this atomic step, other Nodes can skip past us. // Before, we are free of interference from other threads. node.waitStatus = Node.CANCELLED; // If we are the tail, remove ourselves. if (node == tail && compareAndSetTail(node, pred)) { compareAndSetNext(pred, predNext, null); } else { // If successor needs signal, try to set pred's next-link // so it will get one. Otherwise wake it up to propagate. int ws; if (pred ! = head && ((ws = pred.waitStatus) == Node.SIGNAL || (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) && pred.thread ! = null) { Node next = node.next; if (next ! = null && next.waitStatus <= 0) compareAndSetNext(pred, predNext, next); } else { unparkSuccessor(node); } node.next = node; // help GC } }Copy the code
At this point, I think I should clear up the cancellation of the queue.
InterruptedException InterruptedException
In the previous article, we touched on a lot of interrupts, so this is a summary. If you are completely familiar with interrupts, there is no need to read this section, which is written for beginners.
Thread the interrupt
Interrupt is not a command like kill -9 PID. It does not mean that we interrupt a thread and the thread stops running. Interrupts represent thread state. Each thread has an interrupt state associated with it. This is a Boolean value of true or false, starting with false.
There are a few methods to focus on when it comes to interrupted states:
Public Boolean isInterrupted() {} Public Boolean isInterrupted() {} Public Boolean isInterrupted() {} This method returns the interrupted status, resetting the thread's interrupted status to false, so if we call this method twice in a row, Public static Boolean Interrupted () {} Void interrupt() {} Public void interrupt() {}Copy the code
If you interrupt a thread by setting its status to true, what the interrupted thread does with this status is up to the thread itself. Such as the following code:
while (! Thread.interrupted()) { doWork(); System.out.println(" I'm done with one thing, ready to do the next, if no other thread interrupts me "); }Copy the code
Of course, interrupts are more than just thread states, otherwise there is no need to develop a concept.
A thread is automatically aware when it is interrupted if it is in one of three conditions:
-
Wait (), wait(long), wait(long, int) from the Object class,
Join (), join(long), join(long, int), sleep(long), sleep(long, int)
Throws InterruptedException
If a thread blocks on one of these methods (which, as we know, cause the current thread to block), if another thread interrupts the thread, it will immediately return from the methods, throwing InterruptedException and resetting the interrupt status to false.
-
I/O blocking operations in classes that implement the InterruptibleChannel interface, such as the Connect and Receive methods in DatagramChannel
If the thread block here, interrupt threads can lead to throw these methods ClosedByInterruptException and reset the interrupt status.
-
Select method in Selector, which we’ll talk about when we talk about NIO
Once interrupted, the method returns immediately
The above three cases are the most special because they are automatically aware of the interruption (automatic, also based on the underlying implementation) and reset the interrupt state to false after doing the corresponding operation.
Are there only three ways to automatically detect interruptions? No, if the thread is blocked in locksupport. park(Object obj) method, also called suspended, this interrupt will also cause the thread to wake up, but will not reset the interrupted state, so wake up to detect the interrupted state will be true.
InterruptedException overview
It is a special exception, not that the JVM handles it differently, but that it is used in a special way. In general, we see methods like wait() on objects, lockInterruptibly() on ReentrantLock, sleep() on threads, and so on, These methods all carry a throws InterruptedException and are commonly referred to as blocking methods.
One notable feature of blocking methods is that they take a long time (not absolutely, just that time is not in control), and that their method completion returns often depend on external conditions, such as wait depending on notify of another thread, lock depending on UNLOCK of another thread, and so on.
When we see a method with throws InterruptedException, we know that the method should be a blocking method and that we can often interrupt it if we want it to return sooner.
With the exception of a few special classes (Object, Thread, etc.), sensing interrupts and returning ahead of time is done by polling the interrupt state. When we need to write our own interruptible methods, we do so by determining the thread’s interruptible state at the appropriate time (usually at the beginning of the loop) and then doing something about it (usually the method directly returns or throws an exception). Of course, we should also see that if we take a long time to loop, it takes a long time to notice that the thread has broken.
Handling interrupts
Once an interrupt occurs and we receive this information, what do we do about the interrupt? This section will briefly examine this question.
We often write code like this:
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// ignore
}
// go on
Copy the code
When the sleep ends and we continue, we often don’t know if the piece of code actually slept for 10 seconds, or just slept for one second before being interrupted. The problem with this code is that we swallow the exception message. (For the sleep method, I believe that most of the time we don’t care if we interrupt, here’s an example.)
There are two methods of ReentrantLock:
public void lock() {
sync.lock();
}
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
Copy the code
As we mentioned earlier, the lock() method does not respond to interrupts. If thread1 calls the lock() method and does not grab the lock for a long time, thread2 interrupts it. Thread1 does not respond to the request and continues to grab the lock without throwing away the “interrupted” message. We can look at the following code:
public final void acquire(int arg) { if (! TryAcquire (ARG) && acquireQueued(addWaiter(Node.exclusive), ARg)) // tryAcquire(ARg) && acquireQueued(addWaiter(Node.exclusive), ARg)) This way, if the outer method needs to be checked, at least we don't lose this information. SelfInterrupt (); // Thread.currentThread().interrupt(); }Copy the code
For the lockInterruptibly() method, which has a throw InterruptedException, the signal tells us that if we want to cancel the thread lock grab, we simply interrupt the thread and it will return immediately. InterruptedException is thrown.
In parallel packets, there are many examples of this kind of handling of interrupts, providing two methods: responsive interrupt and non-responsive interrupt. In the case of non-responsive interrupt, logging the interrupt rather than losing this information. For example, the two methods in Condition look like this:
void await() throws InterruptedException;
void awaitUninterruptibly();
Copy the code
In general, if the method throws InterruptedException, the first sentence in the method body is:
public final void await() throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); . }Copy the code
The skillful use of interrupts helps us to write elegant code and to analyze other people’s source code.
Reference: www.ibm.com/developerwo…
Translation: www.ibm.com/developerwo…
conclusion
Leave this part to the reader, and I hope you find it rewarding.