Advertisement

“线程池中线程异常后:销毁还是复用?”

阅读量:

目录

一、验证execute提交线程池中

测试

结论

二、验证submit提交线程池中

测试

结论

三、源码解析

查看submit方法的执行逻辑

查看execute方法的执行逻辑

为什么submit方法,没有创建新的线程,而是继续复用原线程?

四、总结


需详细阐述本文使用的是java.util.concurrent.ExecutorService线程池,并将主要围绕验证与源码分析这两个方面来探讨问题。

一、验证execute提交线程池中

测试

我们首先创建了一个名为 ThreadPoolExecutorDeadTest 的类,并将其用于演示 Java 中 ThreadPoolExecutor 的功能。此外该类还用于演示在线程池处理异常时的行为模式。接着在代码实现中 我们模拟了线程池的工作流程:首先创建一个线程池 并依次提交多个待执行的任务 最后观察其在线程池处理异常时的行为模式 其中有一个任务被特意设置为抛出异常 以测试其处理机制

测试代码如下:

复制代码
 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);
    
     }
    
   }
    
 }
    
    
    
    
    java
    
    
![](https://ad.itadn.com/c/weblog/blog-img/images/2025-07-13/Thd6eKl3gHbDufC7GMqRitxwVcEa.png)

通过调用buildThreadPoolExecutor()方法来初始化一个线程池并将结果赋值给变量executorService。该线程池配置有核心线程数量为5个、最大线程数量不超过10个以及空闲后不活跃的线程在3分钟后重新投入队列。任务队列的最大容量被设定为1000,并采用CallerRunsPolicy作为拒绝策略以控制资源使用情况。

然后,通过 executorService.execute() 方法向线程池投递多个任务。其中任务类型为 Runnable`` ,而这些任务将执行 exeTask` 方法。

主线程在完成前五个任务的提交后,在等待期间触发 Thread.sleep(5000) 命令以暂停5秒用于模拟任务执行间隔。随后,在暂停之后向线程池重新提交五个新的任务以继续运行。

结果如下,

可以看到test-2线程不见了,出现了test-3线程。

结论

通过将任务提交至线程池的方式进行操作后发现存在未被捕获的错误时,则会导致该错误的抛出并将导致相应主线程被终止;随后将新主线程加入到该线程池中以继续处理后续任务流程

二、验证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);
    
     }
    
   }
    
 }
    
    
    
    
    java
    
    
![](https://ad.itadn.com/c/weblog/blog-img/images/2025-07-13/qsLBMpFE8u35htzxoaNDQ0jvRZWk.png)

结果如下,

发现test-2线程发生了异常,并未将异常信息压入栈中而仍然被复用,在队列中仍未被删除。

结论

提交到线程池的方式,在运行过程中发生错误时且未被捕获的情况下,则会避免触发异常处理机制,并且也不会启动新的线程以处理该错误。

三、源码解析

查看 submit方法的执行逻辑

在上述测试代码中,“executorService.submit(() -> exeTask("execute"))”处,在 java.util.concurrent.AbstractExecutorService 类中的 submit() 方法内部就可以看到,在 submit 方法中,“RunnableFuture”被包装后调用 execute 方法。

查看execute方法的执行逻辑

调用execute方法后,在其实现中找到并调用**java.util.concurrent.ThreadPoolExecutor#runWorker方法。

可以发现,run方法抛出异常之后,会执行finally。

随后深入进入了java.util.concurrent.ThreadPoolExecutor#processWorkerExit方法

可以发现,如果抛出异常,会移除抛出异常的线程,创建新的线程。

为什么submit方法,没有创建新的线程,而是继续复用原线程?

记得在查看submit方法执行逻辑时,
发现在提交前(也就是在调用execute之前),提交操作会被封装成一个RunnableFuture。
特别地,在其内部实现(即FutureTask)中进行了特别处理。
我们可以通过查阅源码来确认这一细节。

进入 FutureTask的run方法,

在处理过程中,在操作包装时执行了一层任务来捕获异常,并不将其上抛到更高的层次。因此,在处理过程中,在操作包装时执行了一层任务来捕获异常,并不将其上抛到更高的层次;同时也不会去除抛出异常的线程;相反地会启动一个新的线程任务来处理这个问题。

然而,在使用java.util.concurrent.FutureTask.get()时,我们就能获取到相应的异常信息。

四、总结

当一个线程池里面的线程异常后:

在采用execute()执行方式的情况下,在运行过程中若出现堆栈异常的输出信息,则该系统会立即检测到这一异常并采取相应的处理措施:首先将当前被阻塞的线程从队列中移除,并随即生成一个新的线程加入该队列中以接替当前被阻塞的任务。

当执行方式是submit()时,在提交操作的情况下不会触发堆栈异常。然而,在使用Future.get()方法时会捕获并处理了所有可能出现的异常,并将该线程保持在队列中而不生成新的子线程加入队列。

以上俩种执行方式,都不会影响线程池里面其他线程的正常执行。

全部评论 (0)

还没有任何评论哟~