What Happens When a Thread in a Java ThreadPool Throws an Exception?
This article experimentally compares how a Java ExecutorService thread pool reacts to uncaught exceptions when tasks are submitted via execute versus submit, showing that execute removes the faulty thread and creates a new one while submit retains the thread and stores the exception in a Future.
1. Problem Overview
When a thread inside a java.util.concurrent.ExecutorService thread pool throws an uncaught exception, the pool's reaction differs depending on whether the task was submitted with execute or submit. The article investigates this behavior through source‑code verification and analysis.
2. Code Verification – execute
public class ThreadPoolExecutorDeadTest {
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = buildThreadPoolExecutor();
executorService.execute(() -> exeTask("execute"));
executorService.execute(() -> exeTask("execute"));
executorService.execute(() -> exeTask("execute-exception"));
executorService.execute(() -> exeTask("execute"));
executorService.execute(() -> exeTask("execute"));
Thread.sleep(5000);
System.out.println("再次执行任务=======================");
executorService.execute(() -> exeTask("execute"));
executorService.execute(() -> exeTask("execute"));
executorService.execute(() -> exeTask("execute"));
executorService.execute(() -> exeTask("execute"));
executorService.execute(() -> exeTask("execute"));
}
public static ExecutorService buildThreadPoolExecutor() {
return new ThreadPoolExecutor(5, 10, 30, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1000), new ThreadFactoryBuilder().setNameFormat("test-%s").build(),
new ThreadPoolExecutor.CallerRunsPolicy());
}
private static void exeTask(String name) {
String printStr = "[thread-name:" + Thread.currentThread().getName() + ",执行方式:" + name + "]";
if ("execute-exception".equals(name)) {
throw new RuntimeException(printStr + ", 我抛异常了");
} else {
System.out.println(printStr);
}
}
}The execution result (shown in the image) displays a stack trace for the exception. After the exception, the pool removes the faulty worker thread and creates a new one to keep the pool size.
3. Code Verification – submit
public class ThreadPoolExecutorDeadTest {
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = buildThreadPoolExecutor();
executorService.submit(() -> exeTask("execute"));
executorService.submit(() -> exeTask("execute"));
executorService.submit(() -> exeTask("execute-exception"));
executorService.submit(() -> exeTask("execute"));
executorService.submit(() -> exeTask("execute"));
Thread.sleep(5000);
System.out.println("再次执行任务=======================");
executorService.submit(() -> exeTask("execute"));
executorService.submit(() -> exeTask("execute"));
executorService.submit(() -> exeTask("execute"));
executorService.submit(() -> exeTask("execute"));
executorService.submit(() -> exeTask("execute"));
}
public static ExecutorService buildThreadPoolExecutor() {
return new ThreadPoolExecutor(5, 10, 30, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1000), new ThreadFactoryBuilder().setNameFormat("test-%s").build(),
new ThreadPoolExecutor.CallerRunsPolicy());
}
private static void exeTask(String name) {
String printStr = "[thread-name:" + Thread.currentThread().getName() + ",执行方式:" + name + "]";
if ("execute-exception".equals(name)) {
throw new RuntimeException(printStr + ", 我抛异常了");
} else {
System.out.println(printStr);
}
}
}The result image shows that no stack trace is printed. The exception is captured inside the Future returned by submit, and the worker thread remains in the pool; no new thread is created.
4. Source Code Analysis
Key methods examined:
java.util.concurrent.AbstractExecutorService#submit(java.lang.Runnable)– wraps the runnable in a FutureTask, which catches any thrown exception and stores it. java.util.concurrent.ThreadPoolExecutor#runWorker – the core loop that executes tasks; uncaught exceptions cause the worker to exit. java.util.concurrent.ThreadPoolExecutor#processWorkerExit – invoked when a worker terminates; if the termination was due to an exception, the pool removes the thread and may create a replacement.
Because submit uses FutureTask, the exception is considered handled, so processWorkerExit does not treat it as a fatal worker failure. In contrast, execute runs the runnable directly, so an uncaught exception propagates out of runWorker, triggering thread removal.
5. Conclusions
When a task submitted with execute throws an uncaught exception, the pool logs the stack trace, removes the offending thread, and creates a new thread to maintain the configured pool size.
When a task submitted with submit throws an exception, the exception is captured inside the returned Future; the worker thread stays alive, no new thread is created, and the exception can be retrieved via Future.get().
Both submission methods allow other tasks in the pool to continue executing normally.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
JD Tech Talk
Official JD Tech public account delivering best practices and technology innovation.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
