Deep understanding of the nature of Java thread interrupts

  • 2020-04-01 01:26:10
  • OfStack

One, Java interrupt phenomenon
First, look at a few methods in the Thread class:
Public static boolean  interrupted Tests whether the current thread has been interrupted. Thread interrupt status   Cleared by this method. In other words, if the method is called twice in a row, the second call returns false (except when the current thread breaks again, after the first call has cleared the broken state, and before the second call checks the interrupted state). Public boolean  isInterrupted (a) Tests whether the thread has been interrupted. Thread interrupt status   Not affected by this method. Public void  interrupt (a) Interrupt the thread.
It lists several methods and their behavior related to interrupts, and you can see that interrupt is to interrupt a thread. Without an understanding of Java's interrupt mechanism, such an interpretation is highly misleading, assuming that calling a thread's interrupt method is bound to interrupt the thread.
In fact, Java interrupts are a collaborative mechanism. That is, calling interrupt on a thread object does not necessarily interrupt the running thread, it simply requires the thread to interrupt itself at the appropriate time. Each Thread has a Boolean interrupt state (not necessarily a property of the object, and indeed not a field of the Thread), and interrupt simply sets the state to true
 
public class TestInterrupt { 
public static void main(String[] args) { 
Thread t = new MyThread(); 
t.start(); 
t.interrupt(); 
System.out.println(" Of the called thread interrupt methods "); 
} 
static class MyThread extends Thread { 
public void run() { 
int num = longTimeRunningNonInterruptMethod(2, 0); 
System.out.println(" The long-running task run ends ,num=" + num); 
System.out.println(" The interrupted state of a thread :" + Thread.interrupted()); 
} 
private static int longTimeRunningNonInterruptMethod(int count, int initNum) { 
for(int i=0; i<count; i++) { 
for(int j=0; j<Integer.MAX_VALUE; j++) { 
initNum ++; 
} 
} 
return initNum; 
} 
} 
} 

Normally, the following will be printed:
The thread's interrupt method has been called
At the end of the long task run,num=-2
Thread interrupt status :true
As you can see, interrupt does not always interrupt a thread. But what if I changed to the following program?
 
import java.util.concurrent.TimeUnit; 
public class TestInterrupt { 
public static void main(String[] args) { 
Thread t = new MyThread(); 
t.start(); 
t.interrupt(); 
System.out.println(" Of the called thread interrupt methods "); 
} 
static class MyThread extends Thread { 
public void run() { 
int num = -1; 
try { 
num = longTimeRunningInterruptMethod(2, 0); 
} catch (InterruptedException e) { 
System.out.println(" Thread interrupted "); 
throw new RuntimeException(e); 
} 
System.out.println(" The long-running task run ends ,num=" + num); 
System.out.println(" The interrupted state of a thread :" + Thread.interrupted()); 
} 
private static int longTimeRunningInterruptMethod(int count, int initNum) throws InterruptedException{ 
for(int i=0; i<count; i++) { 
TimeUnit.SECONDS.sleep(5); 
} 
return initNum; 
} 
} 
} 

After running, you can see that the program throws an exception and stops, and the last two print statements in the run method are not executed. So what's the difference?
Generally speaking, if a method statement throw InterruptedException, said the method is interruptible (no handling interrupts in the way but also statement throw InterruptedException except), that is to say, interruptible method to interrupt call to respond (such as sleep interrupt response operations include removing the interrupted status, throw InterruptedException), if the interrupt call is before the interruptible method calls, Interruptible methods certainly handle interrupts. In the example above, interrupt is most likely called when run is not in a sleep, but when sleep detects an interrupt, it handles the interrupt. What happens if you call interrupt while a interruptible method is executing? This depends on when the interruptible method handles interrupts, and should handle interrupts as long as the interruptible method detects that the interrupt state is true. Let's add interrupt handling to the code at the beginning.
So how do custom interruptible methods handle interrupts? That is to detect the thread interrupt state and process it where appropriate.
 
public class TestInterrupt { 
public static void main(String[] args) throws Exception { 
Thread t = new MyThread(); 
t.start(); 
// TimeUnit.SECONDS.sleep(1);// If you can't see an interrupt in the process, you can enable this sentence to see the effect again  
t.interrupt(); 
System.out.println(" Of the called thread interrupt methods "); 
} 
static class MyThread extends Thread { 
public void run() { 
int num; 
try { 
num = longTimeRunningNonInterruptMethod(2, 0); 
} catch (InterruptedException e) { 
throw new RuntimeException(e); 
} 
System.out.println(" The long-running task run ends ,num=" + num); 
System.out.println(" The interrupted state of a thread :" + Thread.interrupted()); 
} 
private static int longTimeRunningNonInterruptMethod(int count, int initNum) throws InterruptedException { 
if(interrupted()) { 
throw new InterruptedException(" The thread has been interrupted by the request before formal processing "); 
} 
for(int i=0; i<count; i++) { 
for(int j=0; j<Integer.MAX_VALUE; j++) { 
initNum ++; 
} 
//Suppose this is the right place
if(interrupted()) { 
//Rollback data, cleanup operations, etc
throw new InterruptedException(" The thread is interrupted in the middle of processing "); 
} 
} 
return initNum; 
} 
} 
} 

In the code above, the method longTimeRunningMethod is now an interruptible method. Determine whether the request is interrupted when entering the method, and if so, do not handle it accordingly; There may also be appropriate places to handle interrupts during processing, such as when the innermost loop above ends.
This code detects interrupts using Thread's static method interrupted, which sets the interrupted state to false and returns the previous state, while isInterrupted detects interrupts and does not change the interrupted state. In general, after an interrupt request has been processed, its state should be set to false. But specific still depends on actual situation.

The nature of Java interrupts
Historically, Java has tried to provide pre-emptive restrictive interrupts, but with a lot of problems, such as the deprecated thread.stop, thread.suspend, and thread.resume. On the other hand, for the sake of the robustness of Java application code, it lowers the programming barrier and reduces the probability that programmers who are not aware of the underlying mechanisms will not want to break the system.
Today, thread scheduling in Java does not provide preemptive interrupts, but rather collaborative interrupts. In fact, the cooperative interrupt, the principle is very simple, is polling for an interrupt flag, we can be implemented in any ordinary code. For example, the following code:
 
volatile bool isInterrupted; 
// ...  
while(!isInterrupted) { 
compute(); 
} 

However, the above code problems are also obvious. When compute takes a long time to execute, interrupts cannot be responded to in time. On the other hand, polling to check flag variables makes it difficult to interrupt thread-blocking operations like wait and sleep.
If you still use the above idea, for interrupts to be responded to in a timely manner, you must check the markup variables for thread scheduling at the bottom of the virtual machine. Yes, this is true in the JVM. The following is an excerpt from the java.lang.thread source code:
 
public static boolean interrupted() { 
return currentThread().isInterrupted(true); 
} 
// ...  
private native boolean isInterrupted(boolean ClearInterrupted); 

You can see that isInterrupted is declared as a native method, depending on the underlying implementation of the JVM.
In fact, an interrupt flag is maintained for each thread within the JVM. However, the application cannot directly access this interrupt variable and must operate in the following ways:
 
public class Thread { 
//Set interrupt flag
public void interrupt() { ... } 
//Gets the value of the interrupt flag
public boolean isInterrupted() { ... } 
//Clears the interrupt flag and returns the value of the last interrupt flag
public static boolean interrupted() { ... } 
... 
} 

Typically, calling a thread's interrupt method does not immediately raise an interrupt, but simply sets the interrupt flag within the JVM. Therefore, by checking the interrupt flag, the application can either do something special or ignore the interrupt entirely.

You might think that if the JVM provided only this rudimentary interrupt mechanism, it would have little advantage over an application's own method of defining interrupt variables and polling them.

The main advantage of interrupt variables within the JVM is that there is a mechanism to simulate automatic "interrupt trapping" for some situations.
When performing blocking calls involving thread scheduling (such as wait, sleep, and join), the blocked thread throws InterruptedException "as soon as possible" if an interrupt occurs. Therefore, we can use the following code framework to handle thread blocking interrupts:
 
try { 
//Wait, sleep, or join
} 
catch(InterruptedException e) { 
//Some interrupt handling work
} 

By "as fast as possible," I assume that the JVM checks interrupt variables between thread schedules, depending on the implementation of the JVM and the performance of the hardware.

Some thread-blocking operations that do not throw InterruptedException
However, for certain thread-blocking operations, the JVM does not automatically throw InterruptedException. For example, some I/O operations and internal locking operations. For such operations, interrupts can be simulated in other ways:
1) asynchronous socket I/O in java.io
The read and write methods of InputStream and OutputStream block the wait while reading and writing to the socket, but do not respond to Java interrupts. However, after calling the Socket's close method, the blocked thread throws a SocketException exception.
2) asynchronous I/O using Selector
If the thread is blocked in selecter.select (in java.nio.channels), calling the wakeup method causes the ClosedSelectorException.
3) lock acquisition
If the thread is waiting to acquire an internal lock, we cannot interrupt it. However, with the lockInterruptibly method of the Lock class, we can provide interruption capability while waiting for the Lock.

Four, two programming principles
In addition, in a task-thread-separated framework, the task usually does not know which thread is going to call it, and thus does not know the policy of the calling thread to handle the interrupt. Therefore, there is no guarantee that a task will be canceled after the task is marked with a thread interrupt. Therefore, there are two programming principles:
1) you should not interrupt a thread unless you know its interrupt policy.
This principle tells us that instead of calling the interrupt method of a thread in a framework such as Executer directly, you should cancel the task with a method such as future.cancel.

2) the task code should not guess what the interrupt means to the thread of execution.
This principle tells us that when normal code encounters InterruptedException, it should not be caught and "swallowed", but should continue to be thrown at the upper level.
In short, the non-preemptive interrupt mechanism in Java requires us to change the traditional preemptive interrupt thinking and adopt the corresponding principles and patterns to program on the basis of understanding its nature.

Related articles: