How would you design a long-running worker thread that responds promptly and correctly to cancellation?
answer
- poll isInterrupted() in CPU loops
- catch-and-restore around blocking calls
- cancel via interrupt()/future.cancel(true)/shutdownNow()
- clean up in finally (locks, resources)
- custom stop flag can't wake a blocked thread
basics
~20 sUse interruption: have the worker check Thread.currentThread().isInterrupted() in its loop and catch InterruptedException around its blocking calls (restoring the flag). To cancel, call interrupt() — don't invent your own stop flag if interruption already works.
solid answer
~50 sBuild the worker around the interrupt mechanism so cancellation is prompt at every point. In CPU-bound loops, poll Thread.currentThread().isInterrupted() as the loop condition and exit when it's true. Around every blocking call (sleep, wait, queue take/put), catch InterruptedException, restore the flag, and break out. To cancel, the controller calls workerThread.interrupt() — or, with executors, future.cancel(true) / executor.shutdownNow(), which interrupt the running tasks. Responsiveness depends on how often you reach a checkpoint, so size your work chunks accordingly. Avoid a hand-rolled volatile boolean stop flag as the only mechanism, because it can't wake a thread blocked in sleep/wait — a custom flag and interruption don't compose, and you'd still need interruption to break out of blocking calls. Always leave shared state consistent on exit (release locks via finally, finish or roll back partial work). The mantra: poll the flag in compute loops, catch-and-restore around blocking, cancel via interrupt(), clean up in finally.
code
java · 15 linesRunnable worker = () -> {
try {
while (!Thread.currentThread().isInterrupted()) {
Job job = queue.take(); // interruptible block
process(job); // small chunks -> prompt cancellation
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // restore the cleared flag
} finally {
releaseResources(); // consistent state on every exit path
}
};
Future<?> f = executor.submit(worker);
// ... later, to cancel promptly:
f.cancel(true); // interrupts the running taskgo deeper
Can add a loop that checks isInterrupted() and call interrupt() to stop a simple worker.
Handles both compute loops and blocking calls, knows to cancel via interrupt() rather than a bare boolean flag.
Designs for prompt response and consistent state (finally cleanup), knows future.cancel(true)/shutdownNow(), and the non-interruptible-blocking gotchas (sockets, synchronized).
Establishes cancellation as a cross-cutting design concern: interruptible I/O choices, executor shutdown policy, chunk sizing for responsiveness, and library contracts that propagate cancellation cleanly.
## Goal A worker that (a) stops *soon* after being asked, (b) never corrupts shared state, and (c) plays well with thread pools. Java's **interruption** is the standard tool, but using it well means handling *both* the CPU-loop case and the blocking case, plus cleanup. ## The three checkpoints of a cancellable worker ### 1. Poll the flag in compute loops For work that spins on the CPU (no blocking), the thread must *look* at its **interrupt status flag** periodically — the JVM won't break the loop for you: ```java while (!Thread.currentThread().isInterrupted()) { computeChunk(); // keep chunks small enough for prompt response } ``` Use `isInterrupted()` (non-clearing) so the flag persists for cleanup code. ### 2. Catch-and-restore around blocking calls Blocking methods throw `InterruptedException` and **clear the flag**. Catch it, restore the flag, and unwind: ```java try { Object job = queue.take(); // interruptible block handle(job); } catch (InterruptedException e) { Thread.currentThread().interrupt(); // re-set the cleared flag return; // or break the outer loop } ``` ### 3. Clean up in `finally` Whatever causes exit, leave the world consistent: ```java try { ... } finally { lock.unlock(); // release locks closeResources(); // flush/close, roll back partial work } ``` ## How cancellation is triggered - Raw threads: `workerThread.interrupt();` - Executors: `future.cancel(true)` (the `true` = may-interrupt-if-running) interrupts the task's thread; `executorService.shutdownNow()` interrupts all running tasks. These are the idiomatic, higher-level entry points — they all funnel down to `interrupt()`. ## Why not a custom `volatile boolean stop` flag? A plain stop flag works for CPU loops, but it has a fatal gap: it **cannot wake a thread that is blocked** in `sleep()`, `wait()`, or `queue.take()`. That thread won't see your boolean until the block ends — which may be never. Interruption is wired into those blocking methods precisely to break them out. So even if you add a custom flag, you *still* need `interrupt()` for the blocking case — and now you have two mechanisms to keep in sync. Prefer interruption alone; it covers both cases and composes with the whole `java.util.concurrent` ecosystem. (A custom flag is acceptable only as a *supplement* when you need to carry extra cancellation metadata, never as the *replacement* for interruption.) ## Responsiveness vs. throughput How fast you cancel = how often you hit a checkpoint. Long uninterruptible chunks (e.g. a 5-second matrix multiply with no inner check) delay cancellation by that long. Break big computations into chunks and check the flag between them, balancing the per-check overhead against responsiveness. ## Non-interruptible blocking — the gotcha Some blocking is **not** interruptible: classic `synchronized` lock acquisition, blocking I/O on ordinary `InputStream`/`Socket`, and `Lock.lock()`. To stay cancellable, prefer `Lock.lockInterruptibly()`, NIO `InterruptibleChannel`s (which close and throw on interrupt), or close the socket to unblock the reader. A senior answer should mention that interruption is only as prompt as your *most* uninterruptible operation. ## Terms - **Checkpoint:** a point where the thread can notice cancellation (a flag poll or a blocking call). - **Interruptible vs. uninterruptible blocking:** whether a blocking operation reacts to interrupt() or ignores it. - **future.cancel(true):** request to interrupt a running task managed by an executor.
- Your worker reads from a Socket's InputStream and won't respond to interrupt(). Why, and how do you cancel it?Classic blocking socket I/O is not interruptible, so interrupt() only sets the flag without waking the read. To cancel, close the socket/stream from another thread (the blocked read then throws a SocketException/IOException), or use NIO interruptible channels which close and throw on interrupt.
- What does the boolean argument in future.cancel(true) mean?It is mayInterruptIfRunning: true tells the executor to interrupt the thread already running the task (best-effort prompt cancellation); false only prevents the task from starting if it hasn't begun, leaving a running task to finish.
saying these in an interview costs you the question
- Using only a volatile boolean stop flag (won't wake sleeping/blocked threads)
- Forgetting to clean up shared state / release locks on cancellation
- Doing huge uninterruptible chunks so cancellation is slow
- Assuming all blocking (synchronized, plain socket I/O) is interruptible