Explanation
What just happened
main submitted three tasks and returned. The two pool threads ran them, then went back to waiting for work. They’re normal threads, not daemons, and the JVM only exits when every non-daemon thread has ended. Nobody told the pool there would be no more work, so it never ends.
Try the what-if switch
- shutdown(): the pool accepts no new tasks but finishes the ones it has. With the queue empty, each thread ends, and the JVM exits.
- shutdownNow(): the two running tasks are interrupted mid-sleep, and the queued task is handed back to
mainwithout ever running.
Which one to use
shutdown() then awaitTermination() for a normal end. shutdownNow() when you’re giving up, like on a timeout. On JDK 19+, try-with-resources does shutdown() and the wait for you.
Watch out
submit() after shutdown() throws RejectedExecutionException. And shutdownNow() only interrupts: a task that never checks for interrupts runs on.
Common questions
Why doesn't my Java program exit after main finishes?
A thread pool is still alive. Executors.newFixedThreadPool makes normal (non-daemon) threads, and after their tasks they wait for more work. The JVM waits for them until you call shutdown().
What's the difference between shutdown() and shutdownNow()?
shutdown() rejects new tasks and lets running and queued ones finish. shutdownNow() interrupts running tasks and returns the queued ones without running them.
Does shutdown() wait for tasks to finish?
No, it returns at once. Call awaitTermination() after it, or use try-with-resources: since JDK 19, close() shuts the pool down and waits for every task.
Verified: this timeline matches a real program, checked in CI on JDK 21 and 25. How we verify. Unclear or wrong? Tell us