如果线程池中线程异常后:销毁还是复用?

hi,我是阿昌,今天分享一下如果线程池中线程异常后:销毁还是复用?

下面讨论的线程池是:java.util.concurrent.ExecutorService线程池

通过 execute提交任务

测试代码:

java 复制代码
public static void main(String[] args) {
  ExecutorService executorService = buildThreadPoolExecutor();
  executorService.execute(() -> exeTask("achang-execute"));
  executorService.execute(() -> exeTask("achang-execute"));
  executorService.execute(() -> exeTask("achang-execute-exception"));
  executorService.execute(() -> exeTask("achang-execute"));
  executorService.execute(() -> exeTask("achang-execute"));


  try {
    Thread.sleep(5000);
  } catch (InterruptedException e) {
    throw new RuntimeException(e);
  }
  System.out.println("再次执行任务=======================");

  executorService.execute(() -> exeTask("achang-execute"));
  executorService.execute(() -> exeTask("achang-execute"));
  executorService.execute(() -> exeTask("achang-execute"));
  executorService.execute(() -> exeTask("achang-execute"));
  executorService.execute(() -> exeTask("achang-execute"));
}

public static ExecutorService buildThreadPoolExecutor() {
  return new ThreadPoolExecutor(5, 10, 30, TimeUnit.SECONDS,
                                new LinkedBlockingQueue<>(1000), new ThreadFactoryBuilder().setNameFormat("achang-test-%s").build()
                                , new ThreadPoolExecutor.CallerRunsPolicy());
}

private static void exeTask(String name) {
  String printStr = "[thread-name:" + Thread.currentThread().getName() + ",执行方式:" + name + "]";
  if ("achang-execute-exception".equals(name)) {
    throw new RuntimeException(printStr + ", 我出现了异常了");
  } else {
    System.out.println(printStr);
  }
}

执行结果:

结论:

execute 提交到线程池的方式,如果执行中抛出异常,并且没有在执行逻辑中catch,那么会抛出异常,并且移除抛出异常的线程,创建新的线程放入到线程池中。

通过submit提交任务

测试代码:

java 复制代码
public static void main(String[] args) {
		ExecutorService executorService = buildThreadPoolExecutor();
		executorService.submit(() -> exeTask("achang-submit"));
		executorService.submit(() -> exeTask("achang-submit"));
		executorService.submit(() -> exeTask("achang-submit-exception"));
		executorService.submit(() -> exeTask("achang-submit"));
		executorService.submit(() -> exeTask("achang-submit"));
		
		
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			throw new RuntimeException(e);
		}
		System.out.println("再次执行任务=======================");
		
		executorService.submit(() -> exeTask("achang-submit"));
		executorService.submit(() -> exeTask("achang-submit"));
		executorService.submit(() -> exeTask("achang-submit"));
		executorService.submit(() -> exeTask("achang-submit"));
		executorService.submit(() -> exeTask("achang-submit"));
	}
	
	public static ExecutorService buildThreadPoolExecutor() {
		return new ThreadPoolExecutor(5, 10, 30, TimeUnit.SECONDS,
				new LinkedBlockingQueue<>(1000), new ThreadFactoryBuilder().setNameFormat("achang-test-%s").build()
				, new ThreadPoolExecutor.CallerRunsPolicy());
	}
	
	private static void exeTask(String name) {
		String printStr = "[thread-name:" + Thread.currentThread().getName() + ",执行方式:" + name + "]";
		if ("achang-execute-exception".equals(name)) {
			throw new RuntimeException(printStr + ", 我出现了异常了");
		} else {
			System.out.println(printStr);
		}
	}

执行结果:

结论:

submit 提交到线程池的方式,如果执行中抛出异常,并且没有catch,不会抛出异常,不会创建新的线程。

源码解析

java.util.concurrent.AbstractExecutorService#submit(java.lang.Runnable):

上面的execute会调用到java.util.concurrent.ThreadPoolExecutor#runWorker:

java.util.concurrent.ThreadPoolExecutor#processWorkerExit:

上面可以看出,如果抛出异常,会移除抛出异常的线程,创建新的线程。

那为啥submit()方法,没有创建新的线程,而是继续复用原线程?

看源码可以知道submit也是调用了execute方法,但是在调用之前,包装了一层 RunnableFuture,那一定是在RunnableFuture的实现 FutureTask中有特殊处理了,如下查看源码:

java.util.concurrent.FutureTask#run:

java.util.concurrent.FutureTask#setException:

java.util.concurrent.FutureTask#get():

获取执行结果会调用report()

java.util.concurrent.FutureTask#report:

发现通过s状态位来判断任务状态,来判断是否抛出异常

上面看到通过java.util.concurrent.FutureTask#get(),就可以获取对应的异常信息。

总结

若当线程池里的线程出现了异常后:

  • 如果调用方式是execute时,可以看到堆栈异常的输出,线程池会把这个线程移除掉,并创建一个新的线程放到线程池中。
  • 如果调用是submit时,堆栈异常没有输出。但是调用Future.get()方法时,可以捕获到异常,不会把这个线程移除掉,也不会创建新的线程放入到线程池中。

上面2种调用方式,都不会影响到线程池里面其他线程的正常执行。

相关推荐
Pniubi2 小时前
HashMap的扩容和树化边界
java·开发语言
Mikko73 小时前
用 Java 复刻了高中时在塞班上玩的「熔岩矿车」:纯 Swing、零依赖、没有一张图片,一个 jar 双击就能玩
java·游戏开发·swing
知了学历孙老师3 小时前
小自考的校考和统考,出题方和难度差在哪
java·大数据·前端
Java爱好狂.3 小时前
Java初学者如何一周速通SpringBoot!
java·spring boot·后端·spring·高并发·架构师·java面试
秋饼4 小时前
LLM API密钥动态轮换实战:Spring Vault + Spring AI + 多Provider路由
java·ai·技术分享·后端开发
福兮说5 小时前
JSON 格式正确,数据就可靠吗?我用一份自编配置检查类型与空值
java·前端·json
紧跟先前的步伐7 小时前
Go AI Agent 实战 03|Token、上下文窗口与模型调用成本
java·人工智能·golang
const_qiu7 小时前
第四章 00:开篇 —— 从「会聊天」到「记得住、会干活、能协作」
java·开发语言·人工智能·python
Wang's Blog7 小时前
Java 项目实战: 交友项目 - 个人资料管理的更新接口实现与坑
java·开发语言·交友
Promising_GEO7 小时前
GEE实战02期:从卫星波段开始:利用 Sentinel-2 自己计算 NDVI
java·sentinel·遥感·空间分析