Thread Interruption & Cancellation in Java

In multithreaded programming, stopping a thread safely is one of the most important and misunderstood topics. Unlike some older approaches, Java does not provide a direct way to forcibly kill a thread. Instead, it uses a cooperative interruption mechanism, where a thread is requested to stop and it decides how and when to terminate.

This design ensures better control, avoids resource corruption, and prevents inconsistent program states. In this article, we will explore cooperative interruption, understand InterruptedException, and learn the best practices for stopping threads.

Cooperative Interruption

In Java, thread cancellation is based on cooperation rather than force. When interrupt() is called on a thread, it does not stop the thread immediately. Instead, it sets an internal interrupt flag.

The thread must periodically check this flag and decide whether to terminate.
        Thread t = new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("Working...");
            }
            System.out.println("Thread stopping gracefully");
        });
        t.start();
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t.interrupt(); 
Output:
Working...
Working...
.
.
.
Working...
Thread stopping gracefully
In this example, the thread continuously runs until it detects the interrupt signal using isInterrupted(). Once detected, it exits the loop and terminates gracefully.
Key Idea:
- Interruption is a request, not a command
- The thread decides how to respond
- This avoids unsafe termination

InterruptedException

Certain methods in Java, such as sleep(), wait(), and join(), are blocking operations. When a thread is interrupted while executing one of these methods, Java throws an InterruptedException.
        Thread t = new Thread(() -> {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                System.out.println("Interrupted during sleep");
            }
        });
        t.start();
        t.interrupt();
Output:
Interrupted during sleep
Here, the thread is sleeping, and when interrupt() is called, the sleep is interrupted and an exception is thrown.

When InterruptedException is thrown:

- The thread is woken up from a blocking state (like sleep(), wait(), or join())
- The interrupt flag is cleared
- Control moves to the catch block

But the thread continues executing after the catch block unless you explicitly stop it.
Important Behavior:
When InterruptedException is thrown, Java clears the interrupt flag as part of the exception handling mechanism. This means that after catching the exception, the thread no longer appears to be interrupted.

This becomes a problem because higher-level code may rely on that interrupt signal to make decisions (like stopping execution or cleaning up resources). If you don’t restore the flag, that information is effectively lost.
Thread t = new Thread(() -> {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();// restore interrupt flag 
            }
        });
        t.start();
        t.interrupt();
This ensures that higher-level code is aware of the interruption.

Best Practices for Stopping Threads

Stopping threads incorrectly can lead to serious issues such as data corruption, deadlocks, and resource leaks. The following best practices should always be followed.

1. Never Use stop()

The Thread.stop() method is deprecated and unsafe because it terminates a thread abruptly without giving it a chance to release resources or complete operations.
 //Do NOT use t.stop(); 

2. Use interrupt() for Cancellation

Always use interrupt() to request thread termination. A well-written thread is designed like this:
        Thread t = new Thread(() -> {
            try {
                while (!Thread.currentThread().isInterrupted()) { // do work 
                    Thread.sleep(1000);
                }
            } catch (InterruptedException e) { // interruption during sleep 
                Thread.currentThread().interrupt(); // restore flag
            }
            System.out.println("Thread exiting...");
        });
Now when you call t.interrupt(); the thread will: :

- Exit loop (because flag is set) OR - Catch exception (if sleeping) - Then finish execution β†’ TERMINATED

This allows the thread to clean up resources and exit safely.

3. Check Interrupt Status Regularly

Threads performing long-running tasks should periodically check their interrupt status.
                while (!Thread.currentThread().isInterrupted()) { 
                    // perform work 
                }

4. Handle InterruptedException Properly

Do not ignore InterruptedException. Either handle it or re-interrupt the thread.
catch (InterruptedException e) {
       Thread.currentThread().interrupt();
}

5. Use Volatile Flag (Alternative Approach)

Sometimes, a custom flag is used to control thread execution.
class Worker implements Runnable {
    private volatile boolean running = true;

    public void stop() {
        running = false;
    }

    public void run() {
        while (running) {
            System.out.println("Working...");
        }
    }

    static void main() {
        var worker = new Worker();
        Thread t = new Thread(worker);
        t.start();

        try {
            Thread.sleep(1000); // Let the worker run for a bit
            worker.stop(); // Signal the worker to stop
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
However, this approach does not handle blocking calls, so interrupt() is generally preferred. Because a simple volatile flag only works when the thread is actively running, but it completely fails when the thread is stuck in a blocking call like sleep(), wait(), or join().

6. Combine interrupt() with Cleanup

Always ensure that resources such as files, sockets, or locks are released properly when a thread stops.
        Thread t = new Thread(() -> {
            try {
                while (!Thread.currentThread().isInterrupted()) { // work 
                }
            } finally {
                System.out.println("Cleaning up resources...");
            }
        });

Key Insights

- Java uses cooperative interruption instead of forced termination
- interrupt() sets a flag, not stops execution
- Blocking methods throw InterruptedException
- Proper handling ensures safe and predictable thread termination

Thread interruption and cancellation are fundamental to writing safe and robust multithreaded applications. By adopting a cooperative approach, Java ensures that threads terminate gracefully without compromising system stability.

Mastering interrupt(), understanding InterruptedException, and following best practices will help you build systems that are both efficient and reliable. In the next article, we will explore synchronization and how threads safely share resources
Nagesh Chauhan
Nagesh Chauhan
Principal Engineer | Java Β· Spring Boot Β· Python Β· Microservices Β· AI/ML

Principal Engineer with 14+ years of experience in designing scalable systems using Java, Spring Boot, and Python. Specialized in microservices architecture, system design, and machine learning.

Share this Article

πŸ’¬ Comments

Join the discussion