Historically, Java has tried to provide preemptive limited interrupts, but there have been many problems, such as the abandoned Thread.stop, Thread.suspend and Thread.resume introduced earlier. On the other hand, out of consideration for the robustness of Java application code, the programming threshold is lowered and the probability of programmers who do not know the underlying mechanism unintentionally damage the system is reduced.
Today, Java's thread scheduling does not provide preemptive interrupts, but uses cooperative interrupts. In fact, the principle of cooperative interruption is very simple, which is to poll a certain mark indicating an interruption. We can implement it in any ordinary code.
For example the following code:
volatile bool isInterrupted;
//…
while(!isInterrupted) {
compute();
}
However, the above code problems are also obvious. When the compute execution time is relatively long, the interrupt cannot be responded to in time. On the other hand, by using polling to check flag variables, there is no way to interrupt thread blocking operations such as wait and sleep.
If you still use the above idea, if you want the interrupt to be responded to in a timely manner, you must check the mark variable through thread scheduling at the bottom of the virtual machine. Yes, this is indeed done in the JVM.
The following is excerpted from the source code of java.lang.Thread:
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
//…
private native boolean isInterrupted(boolean ClearInterrupted);
It can be found that isInterrupted is declared as a native method, which depends on the underlying implementation of the JVM.
In fact, the JVM does maintain an interrupt flag internally for each thread. However, the application cannot directly access this interrupt variable and must operate through the following methods:
public class Thread {
//Set interrupt mark
public void interrupt() { ... }
//Get the value of the interrupt mark
public boolean isInterrupted() { ... }
//Clear the interrupt mark and return the value of the last interrupt mark
public static boolean interrupted() { ... }
}
Normally, calling the interrupt method of a thread does not immediately cause an interrupt, but only sets the interrupt flag inside the JVM. Therefore, by checking the interrupt flag, the application can do something special or ignore the interrupt completely.
You may think that if the JVM only provides this crude interrupt mechanism, it has basically no advantage compared to the application's own method of defining interrupt variables and polling.
The main advantage of the JVM's internal interrupt variables is that it provides a mechanism to simulate automatic "interrupt traps" for certain situations.
When executing blocking calls involving thread scheduling (such as wait, sleep, and join), if an interruption occurs, the blocked thread will throw InterruptedException "as quickly as possible". 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 guess the JVM checks the interrupt variable in the gap between thread scheduling. The speed depends on the implementation of the JVM and the performance of the hardware.
However, for certain thread blocking operations, the JVM does not automatically throw InterruptedException. For example, certain I/O operations and internal lock operations. For this type of operation, interrupts can be simulated in other ways:
1) Asynchronous socket I/O in java.io
When reading and writing sockets, the read and write methods of InputStream and OutputStream will block and wait, but will not respond to Java interrupts. However, after calling the Socket's close method, the blocked thread will throw a SocketException.
2) Asynchronous I/O implemented using Selector
If the thread is blocked in Selector.select (in java.nio.channels), calling the wakeup method will cause a ClosedSelectorException exception.
3) Lock acquisition
If a thread is waiting to acquire an internal lock, we cannot interrupt it. However, using the lockInterruptibly method of the Lock class, we can provide interrupt capabilities while waiting for the lock.
In addition, in a framework where tasks and threads are separated, a task usually does not know which thread it will be called by, and therefore does not know the calling thread's strategy for handling interruptions. Therefore, after the task sets the thread interruption flag, there is no guarantee that the task will be canceled. Therefore, there are two programming principles:
1) You should not interrupt a thread unless you know its interrupt policy.
This principle tells us that we should not directly call the interrupt method of a thread in a framework such as Executer, but should use methods such as Future.cancel to cancel tasks.
2) Task code should not guess what the interrupt means to the execution thread.
This principle tells us that when general code encounters an InterruptedException, it should not catch it and "swallow" it, but should continue to throw it to the upper code.
In short, the non-preemptive interrupt mechanism in Java requires us to change the traditional preemptive interrupt idea and adopt corresponding principles and patterns for programming based on understanding its essence.