Bug with a thread interruption: Why doesn't it work when you explicitly interrupt a thread?

Posted by ecko on Sun, 07 Nov 2021 18:12:41 +0100

Hello, I'm Glacier~~

When we call the wait() method of a Java object or the sleep() method of a thread, we need to catch and handle InterruptedException exceptions. If we do not handle InterruptedException exceptions properly, unexpected consequences will occur! Today, as an example, we'll explain in detail why interrupting threads of execution doesn't work.

Procedural Cases

For example, the following program code, the InterruptedTask class implements the Runnable interface. In the run() method, it takes a handle to the current thread, and in the while(true) loop, it uses the isInterrupted() method to detect if the current thread is interrupted, exits the while(true) loop if the current thread is interrupted, and in the while(true) loop, there is a line of Thread.sleep(100) code. An InterruptedException exception was caught. The entire code is shown below.

package io.binghe.concurrent.lab08;

/**
 * @author binghe
 * @version 1.0.0
 * @description Thread Test Interrupt
 */
public class InterruptedTask implements Runnable{

    @Override
    public void run() {

        Thread currentThread = Thread.currentThread();
        while (true){
            if(currentThread.isInterrupted()){
                break;
            }

            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

The intent of the above code is to check if the thread is interrupted by the isInterrupted () method and exit the while loop if it is interrupted. Other threads interrupt the execution thread by calling its interrupt() method, which sets the interrupt flag bit of the execution thread so that currentThread.isInterrupted() returns true, so that it can exit the while loop.

That doesn't look like a problem! But is that true? We create an InterruptedTest class for testing, and the code is shown below.

package io.binghe.concurrent.lab08;

/**
 * @author binghe
 * @version 1.0.0
 * @description Test thread interrupt
 */
public class InterruptedTest {
    public static void main(String[] args){
        InterruptedTask interruptedTask = new InterruptedTask();
        Thread interruptedThread = new Thread(interruptedTask);
        interruptedThread.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        interruptedThread.interrupt();
    }
}

Let's run the main method, as shown below.

That's not what we thought! Dissimilarity! Dissimilarity! Why is that?

problem analysis

The above code explicitly calls the thread's interrupt() method to interrupt the thread, but it does nothing. The reason is that when a thread's run() method executes, it is blocked mostly on sleep(100). When other threads interrupt the execution thread by calling its interrupt() method, the probability of triggering the InterruptedException exception is that while the InterruptedException exception is triggered, the JVM also clears the thread's interrupt flag bit. The currentThread.isInterrupted() determined in the run() method at this point returns false and does not exit the current while loop.

Now that problem analysis is cleared, how do I interrupt the thread and exit the program?

Problem solving

The correct way to handle this is to reset the interrupt flag bit after catching an exception in the while(true) loop of the run() method in the InterruptedTask class, so the code for the correct InterruptedTask class is shown below.

package io.binghe.concurrent.lab08;

/**
 * @author binghe
 * @version 1.0.0
 * @description Interrupt Thread Test
 */
public class InterruptedTask implements Runnable{

    @Override
    public void run() {

        Thread currentThread = Thread.currentThread();
        while (true){
            if(currentThread.isInterrupted()){
                break;
            }

            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
                currentThread.interrupt();
            }
        }
    }
}

As you can see, we have added a new line of code to the catch code block that captures the InterruptedException exception.

currentThread.interrupt();

This allows us to reset the interrupt flag bit of the thread after catching the InterruptedException exception, thus interrupting the currently executing thread.

Let's run the main method of the InterruptedTest class again, as shown below.

summary

Be careful when handling InterruptedException exceptions. If an InterruptedException exception is thrown when calling the interrupt() method of the execution thread to interrupt the execution thread, the JVM will clear the interrupt flag bit of the execution thread at the same time as the InterruptedException exception is triggered, and false is returned when calling the isInterrupted() method of the execution thread. At this point, the correct way to handle this is to catch the InterruptedException exception in the run() method of the executing thread and reset the interrupt flag bit (that is, to call the interrupt() method of the current thread again in the catch code block that captures the InterruptedException exception).

Okay, let's get here today. I'm glacier. We'll see ~~

Topics: Java