Recursion in Threads
A Finish button cannot directly stop a recursive algorithm, because the click can arrive many levels deep in the recursion and all pending calls must return first.
Try it: The Call Stack
How the call stack keeps track of function calls: each call pushes a frame with its own arguments and local variables, and each return pops it.
How it works
- Calling a function pushes a new frame on top of the stack; the caller pauses.
- The frame holds that call's parameters and local variables.
- When the function returns, its frame is popped and the return value goes back to the paused caller.
- A recursive function pushes one frame per call until the base case, then the frames unwind in reverse order.
Default run (17 steps): Program starts: main is about to call factorial(4). … The stack is empty again. Result: 24. Deepest point: 4 frames, 4 calls in total.
Loading the simulation…
Why a Finish Click Cannot Stop Recursion Directly
Many programs let a user cancel a long-running task. In a graphical sorting demo, a Finish button should abort the sort and terminate the thread that is running it. This sounds simple, but a recursive algorithm makes it harder than it first appears. The problem is timing. The Finish button can be clicked at any moment, including when the algorithm is many levels down in its recursion.
A recursive algorithm is not sitting in one simple loop that can be broken out of. It is a stack of method calls, each waiting on the one it made. When the click arrives, the thread is in the middle of several nested calls, and each of those calls is still pending. The source states the consequence directly: before the thread can terminate, all of those recursive method calls must return. A button handler cannot simply reach down into the stack and remove the deepest call.
A Volatile Flag as a Stop Signal
The solution starts with a signal rather than an interruption. Pressing the Finish button sets the value of a boolean variable named running to false. That change is the message to the thread: it should terminate. The thread does not get interrupted directly by the button. Instead, the thread keeps checking the flag at safe points in its work.
The flag is declared volatile. In this demo, the button handler runs on the UI thread and writes the flag, while the worker thread reads it. The volatile declaration is what lets the worker thread see that change, and the source describes this flag as a stop signal that works without a lock.
The flag itself does not stop anything. It only carries the request. Something must read the flag and act on it, and in this demo that job belongs to the delay() method, which the animation calls between steps.
Where the Abort Request Is Read
The delay() method is where the abort request is read. The demo's delay() method checks the running flag twice, once before the sleep and once after. Each check has its own job, and the two together cover the timing problem described above.
- 1First check, before
Thread.sleep(): if running is already false, the thread stops before it starts a new pause. This prevents a thread from beginning a new pause after an abort has already been requested. - 2
Thread.sleep()runs: the thread pauses for the delay. The flag may change while the thread is asleep, because the button handler runs on another thread. - 3Second check, after
Thread.sleep(): if running is now false,delay()throwsThreadTerminationException. This handles the case the source explicitly describes, where the value may have changed during the sleep.
What do you think happens?
If the Finish button sets running to false, does the worker thread stop at that instant?
Reveal answer
No, the flag only carries the request until delay() reads it
Setting the flag changes a value and nothing more. The thread keeps running until it reaches a point where the flag is checked, which in this demo is inside delay().
Unwinding Recursion with an Exception
Once the flag is false, the thread needs every pending recursive call to return. The demo uses an exception for this. QuickSortThreadDemo defines a new exception class, ThreadTerminationException, for this purpose. When delay() sees that running is false, it throws that exception.
An exception propagates up through the chain of method calls. Because each recursive call is waiting on the one it made, the exception passes from the deepest call back through each caller in turn. This unwinds the recursion, and eventually the animation thread itself terminates. The exception does the work that a normal return would otherwise need to do at every level.
Tracing a Click Through a Three-Level Recursion
Abort during the third recursive level
A recursive sort runs in a worker thread. It is three calls deep when the user clicks Finish during a pause. Trace what happens from the click until the thread terminates. Assume the flag is checked in delay() as in the demo.
- 1Click arrives
The Finish handler on the UI thread sets running to false. The worker thread is inside its third call and is currently in
Thread.sleep()insidedelay(). - 2Sleep ends
The worker thread wakes up. Nothing stopped it during the pause, because the flag was only written, not acted on.
- 3Second check fires
delay()reads running after the sleep, sees false, and throwsThreadTerminationExceptionrather than returning normally. - 4Third call unwinds
The exception leaves the third call without that call returning normally. It propagates to the second call, which was waiting on the third.
- 5First call unwinds and thread ends
The exception passes through the second call and then the first call. No pending call finishes its remaining work, and the animation thread terminates.
The abort takes effect at the next check after the click, which here is the second check inside delay(). The exception then removes every pending recursive call, and the thread terminates.
Mistakes When Making Recursion Cancellable
Common mistake Expecting the button click to stop the thread directly
What it looks likeWriting a Finish handler that only sets running to false and assuming the sort halts at that moment.
Why it failsThe flag only carries the request. The thread keeps running until it reaches a point where the flag is read, and a recursive algorithm may be many levels deep when the click arrives.
Do this insteadRead the flag at safe points inside the work, such as inside delay(), and let the exception unwind the recursion.
Common mistake Catching the termination exception inside the recursive method
What it looks likeWrapping the recursive call in a try block that swallows ThreadTerminationException at each level.
Why it failsThe exception is what returns the recursive calls to the top. Catching it inside the recursion stops the unwinding at that level.
Do this insteadLet ThreadTerminationException propagate out of the recursion so every pending call unwinds.
Common mistake Checking the flag only once per pause
What it looks likeChecking running only before Thread.sleep() inside delay().
Why it failsA click during the sleep goes unnoticed until the next call to
delay(), so the abort is delayed.Do this insteadCheck the flag both before and after Thread.sleep(), as the demo does.
Common mistake Treating the boolean as an ordinary variable when the worker thread reads it, instead of declaring it volatile
Practice Tracing an Abort
A recursive algorithm in a worker thread checks a volatile flag only once, at the very start of each recursive call. The user clicks Finish while the algorithm is four calls deep and currently inside a pause that is not preceded by a check. Explain how and when the abort takes effect, and identify which mechanism causes the thread to terminate.
Hints
- Ask which points in the code actually read the flag, and whether the click can happen at a point that is not one of those reads.
- Think about which mechanism carries the termination out of the recursion: the flag, or an exception that propagates up through the calls.
Key Takeaways
- A Finish click cannot stop a deep recursive algorithm directly, because the thread is inside a stack of pending calls that must all return first.
- A volatile boolean such as running works as a stop signal: setting it to false carries the request, but does not stop anything by itself.
- The abort takes effect where the flag is read, which in the demo is
delay(), checked both before and afterThread.sleep(). - Throwing
ThreadTerminationExceptionunwinds every recursive call, and catching it inside the recursion would defeat that purpose.