Appearance
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 theisInterrupted()
flag to determine whether it has been interrupted. If the target thread is in a waiting state, it will catch anInterruptedException
. - A target thread should terminate immediately if it detects that
isInterrupted()
istrue
or if it catches anInterruptedException
. - Proper use of the
volatile
keyword is essential for checking flag variables. - The
volatile
keyword addresses the visibility issue of shared variables among threads.