Details thread interrupt handling in Java programming

  • 2020-04-01 04:25:37
  • OfStack

1. The introduction

When we click the cancel button of an anti-virus software to stop the virus detection, when we type the quit command in the console to end a background service... One thread is needed to cancel the task that another thread is executing. Java does not provide a safe and straightforward way to stop a thread, but it does provide an interrupt mechanism.

Without a comprehensive understanding of Java interrupts, you might be mistaken for thinking that the interrupted thread will immediately exit the run, but that's not the case. How does the interrupt mechanism work? When an interrupt is caught or detected, what happens if you throw InterruptedException or reset the interrupted state and swallow the interrupted state in the method? What are the similarities and differences between thread.stop and interrupts? When do interrupts need to be used? This article will describe from the above aspects.

2. The principle of interruption

The Java interrupt mechanism is a collaborative mechanism, meaning that an interrupt does not terminate another thread directly, but requires the interrupted thread to handle the interrupt itself. It's like parents at home telling their children to pay attention to their bodies, but whether or not they pay attention to their bodies, how they pay attention to their bodies is entirely up to them.

Java interrupt model is so simple, each Thread object has a Boolean type identification (not necessarily to be Thread class field, in fact, it does not, this a few method finally is done through the native method), represents the if there's any interrupt request, the request can be from all threads, including interruption of Thread itself). For example, when thread t1 wants to interrupt thread t2, it only needs to set the interrupt identity of the thread t2 object to true in thread t1, and then thread 2 can choose to process the interrupt request at the appropriate time, or even ignore the request as if the thread was not interrupted.

The java.lang.thread class provides several methods to manipulate the interrupt state, including:


public static booleaninterrupted

Tests whether the current thread has been interrupted. The thread's interrupted state is 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 booleanisInterrupted()

Tests whether the thread has been interrupted. The thread's interrupted state is unaffected by this method.


public void interrupt()

Interrupt the thread.
Among them, interrupt is the only way to set the interrupt state to true. The static method interrupted clears the interrupted state of the current thread, but the naming of this method is highly unintuitive and misleading and requires special attention.

In the above example, thread t1 sets the interrupted state of thread t2 to true by calling interrupt, and t2 can call interrupted or isInterrupted when appropriate to detect the state and handle it accordingly.

In addition, some methods of a class in the class library also may call interrupted, such as the methods of the cancel of FutureTask, if the incoming parameter is true, it will be in the running of asynchronous task thread call interrupt method, if you are in the implementation of the asynchronous task code not to interrupt response, the parameters in the cancel method will not have what effect; Again, the shutdownNow method in ThreadPoolExecutor iterates over the worker thread in the thread pool and invokes the thread's interrupt method to interrupt the thread, so if the task executing in the worker thread does not respond to the interrupt, the task will continue to execute until the normal end.

3. Handling of interrupts

Since the Java interrupt mechanism simply sets the interrupted state of the interrupted thread, what should the interrupted thread do?

Processing time

Obviously, as a collaboration mechanism, the interrupted thread is not forced to process at some point. In fact, the interrupted thread only needs to process at the appropriate time, and if there is no appropriate time point, it can not even process at the task processing level, which is the same as not calling the interrupt method. The "right time" is closely related to the business logic that the thread is working on, for example, before entering a potentially blocked and uninterruptible method at each iteration, but most likely not when a critical section updates another object's state, which can cause the object to be in an inconsistent state.

The timing of processing determines the efficiency of the program and the sensitivity of the interrupt response. Frequent checks of the status of interrupts can make the program less efficient, and conversely, fewer checks can make interrupt requests less responsive. If an interrupt request is made and the interrupted thread continues executing for a period of time without causing a disaster to the system, you can put interrupt processing in a place where it is easy to check for interrupts while still ensuring some degree of response sensitivity. When program performance metrics are critical, a test model may be needed to analyze the optimal interrupt detection points to balance performance and responsiveness.

It is dangerous for a Thread to be forced to terminate before it has finished, because it can have completely unexpected consequences, so you see methods like thread.suspend, thread.stop Deprecated.
So you can't just kill a thread, but sometimes it's necessary to let a thread die, or to let it end some waiting state. Elegant way, give the Thread a interrupt signal, let it to decide what to do. For example, in a child Thread to wait for the arrival of some specific conditions, you invoke the Thread. Sleep (10000), the expected Thread to sleep for 10 seconds after wake up, but if this particular conditions ahead, what do you notice about a Thread in the sleeping & # 63; For example, the main thread blocks itself by calling the join method of the child thread to wait for the child thread to finish, but the child thread finds that it cannot finish in a short time, so it needs to find a way to tell the main thread not to wait for me.
Interrupts are done by calling thread.interrupt (), which tells a Thread that it has been interrupted by modifying its interrupted state. For threads in a cancleable blocking state, such as threads waiting on these functions, thread.sleep (), object.wait (), and thread.join (), the Thread receives an interrupt signal, throws InterruptedException, and sets the interrupted state back to false.
The following program will demonstrate the interruption of a non-blocking thread:


public class Thread3 extends Thread{
  public void run(){
    while(true){
      if(Thread.interrupted()){
        System.out.println("Someone interrupted me.");
      }
      else{
        System.out.println("Going...");
      }
      long now = System.currentTimeMillis();
      while(System.currentTimeMillis()-now<1000){
        //To avoid the confusion of understanding that thread.sleep () needs to catch InterruptedException,
        //Idling here in this way for 1 second
      }
    }
  }
  
  public static void main(String[] args) throws InterruptedException {
    Thread3 t = new Thread3();
    t.start();
    Thread.sleep(3000);
    t.interrupt();
  }
}

The following program demonstrates a child thread telling the parent thread not to wait for it:


public class Thread4 extends Thread {
  private Thread parent;
  public Thread4(Thread parent){
    this.parent = parent;
  }
  
  public void run() {
    while (true) {
      System.out.println("sub thread is running...");
      long now = System.currentTimeMillis();
      while (System.currentTimeMillis() - now < 2000) {
        //To avoid the confusion of understanding that thread.sleep () needs to catch InterruptedException,
        //Idling here in this way for 2 seconds
      }
      parent.interrupt();
    }
  }
  
  public static void main(String[] args){
    Thread4 t = new Thread4(Thread.currentThread());
    t.start();
    try {
      t.join();
    } catch (InterruptedException e) {
      System.out.println("Parent thread will die...");
    }
  }
}

The interrupted state can be read through thread.isinterrupted (), and the state can be read and cleared by a static method called thread.interrupted () (that is, the interrupted state becomes false when the method is called).
Since it is sometimes disadvantageous for a thread in a blocking state to throw an exception and return it to the interrupt state after it is interrupted, because this interrupt state may be used as a judgment condition for other threads, it is safe to reset the state where exception is handled:


boolean interrupted = false;
try {
  while (true) {
    try {
      return blockingQueue.take();
    } catch (InterruptedException e) {
      interrupted = true;
    }
  }
} finally {
  if (interrupted){
    Thread.currentThread().interrupt();
  }
}

When the code needs to throw an InterruptedException, you can either reset the interrupted state or throw InterruptedException outward, as determined by the outer caller.
Not all blocking methods can cancel the blocking state after receiving an interrupt. The input and output stream classes block until I/O completes, but they do not throw InterruptedException and do not exit the blocking state if interrupted.
An attempt to acquire an internal lock (enter a synchronized block) cannot be interrupted, but ReentrantLock supports the interruptible acquisition pattern tryLock(long time, TimeUnit unit).

handling

(1) management of interrupt state

In general, when an InterruptedException is thrown in a potentially blocking method declaration that implies that the method is interruptible, such as BlockingQueue#put, BlockingQueue#take, Object#wait, Thread#sleep, etc., what should the program do if it catches an InterruptedException thrown by such interruptible blocking methods or detects an interrupt? There are two general principles:

If an InterruptedException is thrown by a interruptible blocking method, you can continue to throw the exception to the upper layer of the method call stack, or if an interrupt is detected, you can clear the interrupted state and throw InterruptedException, making the current method an InterruptedException.
If sometimes is not easy to throw InterruptedException methods, such as an interface to implement the method signature on no throws InterruptedException, then can catch InterruptedException and through the Thread of interruptible method. The currentThread. Interrupt () to reset the interrupted status. This is also true if the interrupt state is detected and cleared.
In general code, especially as a base library, you should never swallow interrupts, which means that when you catch InterruptedException, you do nothing in catch, you clear the interrupted state, you do not reset the interrupted state, you do not throw InterruptedException, etc. Because swallowing up the interrupt state causes the upper layer of the method call stack to lose this information.

Of course, there are always exceptions to this rule, when you know exactly who is going to call your method and the caller won't have trouble swallowing the interrupt.

Basically, you want the upper layers of the method call stack to know that an interrupt is happening. Suppose you write a class libraries, a methods amethod class libraries, in amethod to detect and remove the interrupted status, without throwing InterruptedException, as amethod of users, he does not know the details of the inside, if the user after the call amethod to also want to use interrupt to do something, then he will never detect after call amethod interrupted, because information is amethod to clear away. What if you, as a user, encounter such a problematic library and cannot modify your code? You have to set your own interrupt state in your own class and set it when you call interrupt, which is really the last thing you want to do.

(2) response to interrupt

How should the program respond to an interrupt? It all depends on the situation. Some programs may terminate the thread as soon as an interrupt is detected, and some may exit the currently executing task and proceed to the next task... As a collaboration mechanism, this is done by negotiating with the interrupt party and knowing in advance what will happen when called interrupt, such as doing some transaction rollback, some cleanup, some compensation, and so on. You should not interrupt a thread if you are not sure how it will respond to calling its interrupt.

4. Thread. Interrupt VS Thread. Stop

The thread.stop method is no longer recommended. Thread.stop is similar to the interrupt mechanism in some ways. Like when a thread is waiting for a built-in lock or IO, stop, like interrupt, does not abort the operation. When you catch an exception caused by stop, the program can continue executing, even though stop is meant to stop the thread, which makes the behavior of the program more chaotic.

So what's the difference? The most important thing is that the interrupt needs to be detected and handled by the program itself, while thread.stop throws a ThreadDeath Error directly during the execution of the code, which is a subclass of java.lang.error.

Before moving on, here's a quick example:


package com.ticmy.interrupt;
import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.TimeUnit;
public class TestStop {
 private static final int[] array = new int[80000];
 private static final Thread t = new Thread() {
 public void run() {
  try {
  System.out.println(sort(array));
  } catch (Error err) {
  err.printStackTrace();
  }
  System.out.println("in thread t");
 }
 };
 
 static {
 Random random = new Random();
 for(int i = 0; i < array.length; i++) {
  array[i] = random.nextInt(i + 1);
 }
 }
 
 private static int sort(int[] array) {
 for (int i = 0; i < array.length-1; i++){
  for(int j = 0 ;j < array.length - i - 1; j++){
  if(array[j] < array[j + 1]){
   int temp = array[j];
   array[j] = array[j + 1];
   array[j + 1] = temp;
  }
  }
 }
 return array[0];
 }
 
 public static void main(String[] args) throws Exception {
 t.start();
 TimeUnit.SECONDS.sleep(1);
 System.out.println("go to stop thread t");
 t.stop();
 System.out.println("finish main");
 }
}

This example is very simple, thread t did a very time-consuming sort operation, sort method, only simple addition, subtraction, assignment, comparison, and so on, a possible result is as follows:


 
go to stop thread t
java.lang.ThreadDeath
 at java.lang.Thread.stop(Thread.java:758)
 at com.ticmy.interrupt.TestStop.main(TestStop.java:44)
finish main
in thread t

The sort method is a very time-consuming operation, meaning that when the main thread calls stop after a second of sleep, the thread t is still performing the sort method. Even such a simple method can throw an error! In other words, after a stop call, most Java bytecode is likely to throw an error, even a simple addition!

If the thread is currently holding a lock, the lock is released after stop. Because this error can occur in so many places, it can be hard for programmers to guard against and can easily lead to inconsistent object state. For example, the object obj holds a range value: minimum low, maximum high, and low must not be greater than high. This relationship is protected by a lock lock to avoid the failure of the relationship due to race conditions during concurrency. If the current low value is 5 and the high value is 10, when the thread t acquires the lock, it updates the low value to 15 and then it is stopped, which is really bad. If the Error caused by stop is not caught, the low value will be 15 and the high value will still be 10. As a result, the less than relationship between them cannot be guaranteed, that is, the object state is broken! An Error caused by catching a stop while assigning a low might cause the later assignment of the high variable to continue, but no one knows which statement the Error will be thrown. What if the relationship between object states is more complex? This approach is almost impossible to maintain. It's too complicated! If the operation is interrupted, it is determined not to throw an error while performing a low assignment, so that the program can control the consistency of the object state.

It is because of the possibility of inconsistent object state that stop is disabled.

5. Use of interrupts

In general, interrupts are used in the following scenarios:

When you click the cancel button in a desktop application;
When an operation exceeds a certain execution time limit and needs to be aborted;
When multiple threads do the same thing, as long as one thread succeeds, the others can cancel.
When one or more of a group of threads fails to continue due to an error.
When an application or service needs to be stopped.
Let's look at a specific example. In this example, we intended to use GUI form, but considering that GUI code would complicate the program, we used the console to simulate the core logic. A new task for disk file scanning, scanning all the files in a directory and printing the file path to the console, can take a long time. To abort the task, simply type quit in the console and press enter.


package com.ticmy.interrupt;
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;

public class FileScanner {
 private static void listFile(File f) throws InterruptedException {
 if(f == null) {
  throw new IllegalArgumentException();
 }
 if(f.isFile()) {
  System.out.println(f);
  return;
 }
 File[] allFiles = f.listFiles();
 if(Thread.interrupted()) {
  throw new InterruptedException(" The file scanning task is interrupted ");
 }
 for(File file : allFiles) {
  //You can also put interrupt detection here
  listFile(file);
 }
 }
 
 public static String readFromConsole() {
 BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
 try {
  return reader.readLine();
 } catch (Exception e) {
  e.printStackTrace();
  return "";
 }
 }
 
 public static void main(String[] args) throws Exception {
 final Thread fileIteratorThread = new Thread() {
  public void run() {
  try {
   listFile(new File("c:\"));
  } catch (InterruptedException e) {
   e.printStackTrace();
  }
  }
 };
 new Thread() {
  public void run() {
  while(true) {
   if("quit".equalsIgnoreCase(readFromConsole())) {
   if(fileIteratorThread.isAlive()) {
    fileIteratorThread.interrupt();
    return;
   }
   } else {
   System.out.println(" The input quit Exit file scan ");
   }
  }
  }
 }.start();
 fileIteratorThread.start();
 }
}

In the process of scanning the file, the detection of interrupts adopted here is that, if the file is encountered, do not detect the interrupt, is the directory to detect the interrupt, because the files may be very many, every time the detection of a file will reduce the execution efficiency of the program. In addition, in the fileIteratorThread thread, only InterruptedException was caught without resetting the interrupted state or continuing to throw an exception, because I was well aware of its usage, and there were no methods at the top of the call stack for the run method that might need to detect the interrupted state.

In this program, the input quit can perform the operation system.exit (0) to exit the program, but as mentioned earlier, this is a simulation of the core logic of the GUI program, in the GUI, executing system.exit (0) will cause the entire program to exit.


Related articles: