Skip to content

Interrupting Threads

If a thread needs to perform a long-running task, it may be necessary to interrupt it. Interrupting a thread sends a signal from another thread to the target thread, which, upon receiving the signal, should terminate the execution of the run() method, allowing the thread to end immediately.

Example

Consider an example where a file of 100MB is being downloaded over a slow network. If the user gets impatient and clicks "Cancel," the program needs to interrupt the download thread.

Interrupting a thread is simple; you only need to call the interrupt() method on the target thread from another thread. The target thread must repeatedly check its own status using the isInterrupted() method. If it detects that it has been interrupted, it should terminate its execution immediately.

Here’s an example code snippet:

java
// Interrupting Threads
public class Main {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new MyThread();
        t.start();
        Thread.sleep(1); // Pause for 1 millisecond
        t.interrupt(); // Interrupt thread t
        t.join(); // Wait for thread t to finish
        System.out.println("end");
    }
}

class MyThread extends Thread {
    public void run() {
        int n = 0;
        while (!isInterrupted()) {
            n++;
            System.out.println(n + " hello!");
        }
    }
}

In the above code, the main thread interrupts thread t by calling t.interrupt(). However, it's important to note that the interrupt() method only sends an "interruption request" to thread t. Whether t responds immediately depends on the code it executes. Since the while loop in t checks isInterrupted(), the code correctly responds to the interrupt() request and terminates the run() method.

If the thread is in a waiting state (for example, if t.join() causes the main thread to wait), calling interrupt() on the main thread will immediately throw an InterruptedException. Therefore, when the target thread catches this exception, it indicates that another thread has called interrupt() on it, and it should terminate.

Another Example

Let’s look at another example code snippet:

java
// Interrupting Threads
public class Main {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new MyThread();
        t.start();
        Thread.sleep(1000);
        t.interrupt(); // Interrupt thread t
        t.join(); // Wait for thread t to finish
        System.out.println("end");
    }
}

class MyThread extends Thread {
    public void run() {
        Thread hello = new HelloThread();
        hello.start(); // Start the hello thread
        try {
            hello.join(); // Wait for the hello thread to finish
        } catch (InterruptedException e) {
            System.out.println("interrupted!");
        }
        hello.interrupt();
    }
}

class HelloThread extends Thread {
    public void run() {
        int n = 0;
        while (!isInterrupted()) {
            n++;
            System.out.println(n + " hello!");
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                break;
            }
        }
    }
}

In this case, the main thread notifies thread t to interrupt it while thread t is waiting at hello.join(). This method will immediately end the waiting and throw an InterruptedException. Since we catch this exception in thread t, we can prepare to terminate it. Before t ends, it also calls interrupt() on the hello thread to notify it to interrupt. If this line is removed, the hello thread will continue running, and the JVM will not exit.

Using Flag Variables for Interruption

Another common way to interrupt a thread is by using a flag variable. We typically use a running flag to indicate whether a thread should continue running. By setting HelloThread.running to false in the external thread, we can allow the thread to terminate:

java
// Interrupting Threads
public class Main {
    public static void main(String[] args) throws InterruptedException {
        HelloThread t = new HelloThread();
        t.start();
        Thread.sleep(1);
        t.running = false; // Set the flag to false
    }
}

class HelloThread extends Thread {
    public volatile boolean running = true;
    public void run() {
        int n = 0;
        while (running) {
            n++;
            System.out.println(n + " hello!");
        }
        System.out.println("end!");
    }
}

Note that the running flag in HelloThread is a shared variable between threads. Shared variables between threads should be marked with the volatile keyword to ensure that every thread can read the updated variable value.

Why Use the volatile Keyword?

The volatile keyword ensures visibility of changes to variables across threads. In the Java Virtual Machine (JVM), variable values are stored in main memory, but when a thread accesses a variable, it first retrieves a copy to its working memory. If a thread modifies the variable, the JVM may write the modified value back to main memory at some point, but this timing is unpredictable!

┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐
           Main Memory
│                               │
   ┌───────┐┌───────┐┌───────┐
│  │ var A ││ var B ││ var C │  │
   └───────┘└───────┘└───────┘
│     │ ▲               │ ▲     │
 ─ ─ ─│─│─ ─ ─ ─ ─ ─ ─ ─│─│─ ─ ─
      │ │               │ │
┌ ─ ─ ┼ ┼ ─ ─ ┐   ┌ ─ ─ ┼ ┼ ─ ─ ┐
      ▼ │               ▼ │
│  ┌───────┐  │   │  ┌───────┐  │
   │ var A │         │ var C │
│  └───────┘  │   │  └───────┘  │
   Thread 1          Thread 2
└ ─ ─ ─ ─ ─ ─ ┘   └ ─ ─ ─ ─ ─ ─ ┘

This can lead to a situation where if one thread updates a variable, another thread might read the old value. For example, if the main memory variable a is true, and thread 1 sets a to false, at that moment, it only changes the copy of variable a in its own memory. The main memory variable a remains true until the JVM writes back the modified a, so other threads may still read the old value true, leading to inconsistencies in shared variables among threads.

Therefore, the purpose of the volatile keyword is to tell the JVM to:

  • Always fetch the latest value from the main memory when accessing the variable.
  • Immediately write back to the main memory when modifying the variable.

The volatile keyword addresses the visibility issue: when one thread modifies the value of a shared variable, other threads can immediately see the updated value.

If we remove the volatile keyword, the behavior of the program may appear similar, but this is due to the fact that, in x86 architecture, the JVM writes back to main memory very quickly. However, in ARM architecture, there can be significant delays.

Summary

  • Calling the interrupt() method on a target thread requests that it be interrupted. The target thread checks the isInterrupted() flag to determine whether it has been interrupted. If the target thread is in a waiting state, it will catch an InterruptedException.
  • A target thread should terminate immediately if it detects that isInterrupted() is true or if it catches an InterruptedException.
  • Proper use of the volatile keyword is essential for checking flag variables.
  • The volatile keyword addresses the visibility issue of shared variables among threads.
Interrupting Threads has loaded