Skip to content
Java synchronization 6 min read

Interrupting a Thread

Sometimes you need to ask a running thread to stop what it is doing — cleanly, cooperatively, and without pulling the rug out from under it. Java’s thread interruption mechanism is exactly that cooperative signal.

What Is Thread Interruption?

Java does not let you forcibly kill a thread (the old Thread.stop() method was deprecated and removed because it could leave shared data in a corrupted state). Instead, it gives you a polite tap on the shoulder: an interrupt flag on every Thread object.

When you call thread.interrupt(), Java sets that flag to true. The running thread is expected to notice this flag and shut itself down gracefully. The thread is in control of its own exit — you are just asking nicely.

Note: Calling interrupt() does not stop the thread immediately. The thread continues running until it checks the flag or blocks on an interruptible operation.

The Three Key Methods

MethodWhat it does
thread.interrupt()Sets the interrupt flag on the target thread
Thread.interrupted()Static — checks AND clears the flag for the current thread
thread.isInterrupted()Instance — checks the flag without clearing it

Understanding this difference is important: Thread.interrupted() has a side effect — after calling it, the flag is reset to false. isInterrupted() is a read-only check you can call repeatedly.

Basic Example: Checking the Flag in a Loop

The simplest pattern is to check isInterrupted() inside your thread’s work loop and break out when it is set.

public class CounterThread extends Thread {

    @Override
    public void run() {
        int count = 0;
        while (!isInterrupted()) {         // check before each iteration
            System.out.println("Count: " + count++);
            // simulate some work (without sleeping)
        }
        System.out.println("Thread stopped gracefully at count: " + count);
    }

    public static void main(String[] args) throws InterruptedException {
        CounterThread t = new CounterThread();
        t.start();
        Thread.sleep(1);   // let it run briefly
        t.interrupt();     // ask it to stop
        t.join();
        System.out.println("Main thread done.");
    }
}

Output:

Count: 0
Count: 1
...
Thread stopped gracefully at count: 47
Main thread done.

The exact count will vary by system speed. The key point is that the thread exits the loop and prints its goodbye message — no abrupt crash.

Handling InterruptedException

Many blocking operations — Thread.sleep(), Object.wait(), BlockingQueue.take(), and others — declare throws InterruptedException. When a thread is blocked in one of these calls and you call interrupt() on it, two things happen:

  1. The blocking call throws InterruptedException.
  2. The interrupt flag is cleared (reset to false).

This means if you catch InterruptedException and swallow it silently, you have lost the signal. Always either re-interrupt the thread or propagate the exception.

public class SleepingThread extends Thread {

    @Override
    public void run() {
        try {
            System.out.println("Thread going to sleep...");
            Thread.sleep(10_000);   // would sleep for 10 seconds
            System.out.println("This line won't print if interrupted.");
        } catch (InterruptedException e) {
            // Restore the interrupt flag — important!
            Thread.currentThread().interrupt();
            System.out.println("Thread was interrupted while sleeping. Exiting.");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SleepingThread t = new SleepingThread();
        t.start();
        Thread.sleep(100);   // give it a moment to start sleeping
        t.interrupt();
        t.join();
        System.out.println("Main finished.");
    }
}

Output:

Thread going to sleep...
Thread was interrupted while sleeping. Exiting.
Main finished.

Warning: Never do catch (InterruptedException e) { /* ignore */ }. You silently ate a cancellation signal, making the thread impossible to stop from the outside. At minimum call Thread.currentThread().interrupt() to restore the flag.

The Two-Step Pattern (Long-Running + Sleeping)

Many real threads both do CPU work and occasionally block. You need to handle both cases:

public class WorkerThread extends Thread {

    @Override
    public void run() {
        while (!isInterrupted()) {
            // 1. Do CPU work
            processNextItem();

            // 2. Take a short break — interruptible
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                // sleep cleared the flag; restore it so the while-check exits
                Thread.currentThread().interrupt();
            }
        }
        System.out.println("Worker exiting cleanly.");
    }

    private void processNextItem() {
        System.out.println("Processing on thread: " + getName());
    }

    public static void main(String[] args) throws InterruptedException {
        WorkerThread worker = new WorkerThread();
        worker.start();
        Thread.sleep(1200);
        worker.interrupt();
        worker.join();
    }
}

The while (!isInterrupted()) guard catches interrupts that arrive during CPU work, and the catch (InterruptedException) block handles interrupts that arrive during sleep().

Thread.interrupted() vs isInterrupted()

It is easy to pick the wrong one. Here is a concrete example showing the difference:

public class FlagDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            Thread.currentThread().interrupt();   // set the flag

            // isInterrupted() — does NOT clear
            System.out.println("isInterrupted() #1: " + Thread.currentThread().isInterrupted());
            System.out.println("isInterrupted() #2: " + Thread.currentThread().isInterrupted());

            // Thread.interrupted() — CLEARS after first call
            System.out.println("interrupted()   #1: " + Thread.interrupted());
            System.out.println("interrupted()   #2: " + Thread.interrupted());
        });
        t.start();
        t.join();
    }
}

Output:

isInterrupted() #1: true
isInterrupted() #2: true
interrupted()   #1: true
interrupted()   #2: false

Tip: Prefer isInterrupted() inside a loop condition. Use Thread.interrupted() only when you want to consume and reset the flag in one atomic step.

Using Interruption with Executor Services

When you submit tasks to an ExecutorService, call shutdownNow() to interrupt all running threads in the pool. Your Runnable or Callable must still cooperate by checking the flag or handling InterruptedException.

import java.util.concurrent.*;

public class ExecutorInterruptDemo {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService executor = Executors.newSingleThreadExecutor();

        executor.submit(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("Task running...");
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();   // restore flag
                }
            }
            System.out.println("Task cancelled.");
        });

        Thread.sleep(600);
        executor.shutdownNow();   // sends interrupt to all threads
        executor.awaitTermination(2, TimeUnit.SECONDS);
    }
}

See Thread Pools & Executors for more about ExecutorService lifecycle management.

Under the Hood

Every Thread object in the JVM holds a native boolean interrupt flag stored inside the OS thread structure. It is not a Java field — the flag lives at the JVM/OS level, which is why reading and writing it is thread-safe without synchronized or volatile.

When a thread is blocked in a native call (Object.wait, Thread.sleep, LockSupport.park, or any InterruptibleChannel I/O), the JVM:

  1. Sets the flag.
  2. Signals the underlying OS to unblock the waiting call.
  3. Clears the flag.
  4. Throws InterruptedException into the thread.

This unblock-and-clear behavior is why you must restore the flag with Thread.currentThread().interrupt() — otherwise any code above your catch block that checks isInterrupted() will see false and think the thread was never interrupted.

For NIO channel operations (java.nio.channels.InterruptibleChannel), the behavior is slightly different: instead of InterruptedException, the channel is closed and a ClosedByInterruptException is thrown. This is worth knowing if you write network or file I/O code in worker threads.

Note: The volatile keyword and the Java Memory Model guarantee visibility of the interrupt flag across cores without any extra synchronization on your part.

Quick Reference: Best Practices

  • Always handle InterruptedException — never swallow it silently.
  • Re-interrupt with Thread.currentThread().interrupt() after catching.
  • Check isInterrupted() in long CPU-bound loops so the thread can notice an interrupt even without blocking.
  • Design tasks submitted to executor pools to cooperate with interruption — shutdownNow() only works if tasks listen.
  • Avoid the deprecated Thread.stop() — it can leave monitors unlocked and data corrupt.
  • Multithreading — the foundation for understanding Java threads and why interruption matters
  • Thread Life Cycle — see where “interrupted” fits in the full thread state diagram
  • Thread.sleep() — the most common place InterruptedException is encountered
  • Deadlock — interruption can be used as a recovery strategy for deadlocked threads with ReentrantLock
  • ReentrantLock & MonitorslockInterruptibly() lets you acquire locks in a way that respects interrupts
  • Thread Pools & Executors — how shutdownNow() uses interruption to cancel tasks
Last updated June 13, 2026
Was this helpful?