A thread should not be interrupted or stopped by other threads, but should be stopped by the thread itself. With this in mind, violent Thread operations such as thread.stop (), thread.suspend (), and thread.resume () are obsolete.
Isn’t there a way to terminate threads in Java concurrent programming? Of course not. Instead, there is an elegant way to close: Thread.interrupt(). One thing to keep in mind is that interrupt() does not actually interrupt the thread, but modifies a flag bit.
Calling interrupt() from thread B in thread A does not interrupt thread B immediately, but tells thread B: You should interrupt! Whether thread B interrupts is up to it (it can detect that the thread is interrupted and throw InterruptedException; You can also continue running the thread.
If a thread is blocked, calling interrupt() causes the current thread to exit the block immediately. The blocking modes include Object.wait(), thread.sleep (), thread.join (), and locksupport.park ().
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
System.out.println(DateTime.now().toString());
LockSupport.park();
System.out.println(DateTime.now().toString());
System.out.println(Thread.interrupted());
System.out.println(Thread.interrupted());
});
thread.join();
thread.start();
// Make sure the thread starts successfully and is blocked
Thread.sleep(3000);
// Notification is interrupted
thread.interrupt();
}
// Result output
2021-04-23T15:29:53.969+ 08:00
2021-04-23T15:29:56.950+ 08:00 // The thread exits the block immediately after the notification is interrupted
true
false
Copy the code
However, have you noticed that the output of two thread interrupts is inconsistent? Let’s take a look at the source code.
// Tests whether the current thread has been interrupted
public static boolean interrupted(a) {
return currentThread().isInterrupted(true);
}
// Whether the interrupt status is reset depends on the ClearInterrupted value passed.
private native boolean isInterrupted(boolean ClearInterrupted);
Copy the code
Note that the Boolean type argument ClearInterrupted is obvious enough.
Interrupt () doesn’t really interrupt a thread, it just modifies one of its internal state values.
Pre-knowledge understanding is completed, next let us enter the topic!
Sample code:
public static void main(String[] args) throws InterruptedException {
final ReentrantLock lock = new ReentrantLock();
// The thread runtime body
Runnable runnable = () -> {
try {
lock.lockInterruptibly();
System.out.println(Thread.currentThread().getName() + "Acquire lock, execute business logic!");
Thread.sleep(3000);
} catch (Exception e) {
e.printStackTrace();
} finally{ lock.unlock(); }};// Create two threads
Thread thread1 = new Thread(runnable, "thread-1");
Thread thread2 = new Thread(runnable, "thread-2");
thread1.start();
// Make sure thread-2 is started before thread-1
Thread.sleep(500);
thread2.start();
// Sleep for one second to inform thread2 of interruption
Thread.sleep(1000);
thread2.interrupt();
}
Copy the code
Run the code, console output:
thread-1Get the lock and execute the business logic! java.lang.InterruptedException at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
at com.edu.ReentrantLockTestInterrupt.lambda$main$0(ReentrantLockTestInterrupt.java:18)
at java.lang.Thread.run(Thread.java:748)
Exception in thread "thread-2" java.lang.IllegalMonitorStateException
at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261)
at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:457)
at com.edu.ReentrantLockTestInterrupt.lambda$main$0(ReentrantLockTestInterrupt.java:25)
at java.lang.Thread.run(Thread.java:748)
Copy the code
Let’s see what happens when the code runs:
- Thread1 runs, acquires the lock, and sleeps for the next 3 seconds (simulating a business scenario);
- After thread1 runs for 0.5 seconds, thread2 starts to run. As the lock is held by Thread1, thread2 fails to obtain the lock and enters the waiting queue, blocking.
- After a thread1 runs for 1.5 seconds, thread2 is notified to interrupt the thread, and thread2 immediately exits the blocking state. Since the interrupt lock is used, Thread2 responds to the interrupt signal by throwing InterruptedException; Perform in the finally block unlock logic, as a result of thread2 didn’t get to the lock, non-exclusive threads, throw IllegalMonitorStateException.
Previously: ReentranLock lock and unlock process, described in detail in the last blog.
# java.util.concurrent.locks.AbstractQueuedSynchronizer
public final void acquireInterruptibly(int arg) throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if(! tryAcquire(arg)) doAcquireInterruptibly(arg); }private void doAcquireInterruptibly(int arg) throws InterruptedException {
// Wrap the current thread into a node of exclusive type and add it to the end of the waiting queue
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;
}
In a fair lock, instead of throwing an exception, the local variable interrupted is set to true
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw newInterruptedException(); }}finally {
if(failed) cancelAcquire(node); }}Copy the code
If you took a close look at the last blog post, you will see that the logic for entering a blocking queue in interrupt mode is essentially the same as that for (non-) fair locks, except that InterruptedException is thrown when the thread is interrupted. Fair locks return a Boolean value that specifies whether the thread is interrupted.
One thing that needs to be mentioned here is that the request lock method acquire() for fair locks has a selfInterrupt(); The logic. You can read it literally: self-interruption. Why is there such a logic?
Note: after the thread detects the interrupted state, it will clear the value of the state!
In fair lock mode, if thread A is still blocking in the wait queue, thread B calls interrupt() and thread A immediately exits the blocking state, modifying the thread local variable interrupted = true. Since thread A may not be A candidate thread, it continues to block after A loop. Returns the interrupted variable (true) until the current thread becomes a candidate and is woken up to acquire the lock. SelfInterrupt () is repeated to set back the cleared state. If an interrupt detection throw occurs in a subsequent operation (such as a synchronized block), an interrupt request from thread B is answered.
After thread2 responds to the interrupt request, throw new InterruptedException() to break the loop. Since the failed value is true, execute cancelAcquire(node).
private void cancelAcquire(Node node) {
if (node == null)
return;
node.thread = null;
// Remove the precursor node in the cancelled state
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
// Pred is the nearest normal precursor node in the direction of the node head.
Node predNext = pred.next;
Use unconditional write instead of CAS. After this atomic step, other nodes can skip us.
// waitStatus is volatile
node.waitStatus = Node.CANCELLED;
// If node is a tail node, set its precursor to the tail node
if (node == tail && compareAndSetTail(node, pred)) {
// Set the backend node of the successfully set tail node to NULL help Gc
compareAndSetNext(pred, predNext, null);
} else {
// Node is not a tail node
// Wake up the back-end node if pred is the head node, or is in a non-notifying state (CAS failed), or does not hold threads
// Otherwise, CAS sets the rear drive of pred to next
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