An in depth overview of the nature and programming principles of Java thread interrupts

  • 2020-04-01 01:46:12
  • OfStack

Historically, Java has tried to provide preemptively limited interrupts, but with a lot of problems, such as the deprecated thread.stop, thread.suspend, and thread.resume described earlier. 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 (). The 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 the 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.      

      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.
      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: