Spring异步线程池的问题

发布于:2024-10-11 ⋅ 阅读:(97) ⋅ 点赞:(0)

今天看一视频,提到说 Spring默认的异步线程池比较简单,每次执行异步任务,都会新建一个线程进行处理,不会重复利用,所以在用Spring框架开发的时候,需要自定义异步线程池。第一次听到这个说法。遂开始百度。

百度 “Spring 默认的异步线程池的配置”

不到三秒看到了这么一个博客: https://www.cnblogs.com/zhaoyuan72/p/16347289.html

看到了 原文地址是从知乎来的,遂跳转到知乎:https://zhuanlan.zhihu.com/p/346086161。

我这个人有个毛病,干什么都想看到最原始的那个东西,比如听歌就要听原唱,不听改编翻唱,好吧,其实这样没必要。

作者开篇提到

上一篇分享了JDK自带的线程池 ThreadPoolExecutor 的配置和参数详解,然而我们实际开发中更多的是使用SpringBoot来开发,Spring默认也是自带了一个线程池方便我们开发,它就是ThreadPoolTaskExecutor,接下来我们就来聊聊Spring的线程池吧。

就是说 JDK自带的线程池是 ThreadPoolExecutor,而Spring框架又自我封装了一个线程池 ThreadPoolTaskExecutor。说实话,这个思想就是这样,JDK本身有自带的东西,很多框架觉得JDK的东西不好,都会自己二次改造,再来一个。

比如说JDK默认的序列化机制速度慢,生成的字节占用空间大,用的是java.io.ObjectInputStream 和 ObjectOutputStream。所以一般我们也不会用JDK的序列化机制,比如前后端交互我们用JSON,JSON又可以用FastJson或者Jackson等等。其他的场景如一个RPC框架我们可以用 Java 下面的最快的序列化工具 Kyro。(举例可能不恰当,但是就是这么个意思)

再比如说 Spring也有些东西,可能其他框架觉得它不太好,也会自己写一个。比如 StringUtils 这个类,一般开发中可能都会用 common.lang3的StringUtils,MP也会封装自己的 StringUtils,Hutool也会封装自己的StringUtils。

突然就理解了 common.lang 和 common.lang3 以及 fastjson 和fastjson2 的关系了。
commons.lang3 就是 common.lang 的3.x版本, 但是应该是一次大版本升级,所以重新搞成了一个artifact,fastjson2也是如此。
common.lang3的一个版本一定是从3.x开始的。fastjson2也一定是从2.x开始的

我们大致看一下 ThreadPoolExecutor。
参考了这篇文章。
https://blog.csdn.net/cy973071263/article/details/131484311 。

大致看完这个,开始看Spring的 ThreadPoolTaskExecutor。就看之前知乎的那一篇文章,作者先说的是 SimpleAsyncTaskExecutor,

在这里插入图片描述

在这里插入图片描述

于是我决定执行自己的demo

在这里插入图片描述

发现几个问题
1.没有如作者博客一样,我的demo线程复用了,没有一直创建,但是默认好像是8个线程
2.没有如博客作者一样打印出来 SimpleAsyncTaskExecutor 这个类

于是,去看了一下博客的日期,距今(2024年0925日)已经三年多了,Spring我目前用的都是5.3的版本了

在这里插入图片描述
看到了这个,可以去翻源码了,猜想是不是新版本Spring 默认的 concurrencyLimit 是8

在这里插入图片描述

看源码,全局搜 concurrencyLimit。

在这里插入图片描述
卧槽,那岂不是沙雕了。

又看一下我之前设置 的定时默认线程数是 8,但是我已经全部注释掉了呀
在这里插入图片描述

于是,开始去找新的博客看看。这次搜索的词条是:

Spring默认的异常线程池 SimpleAsyncTaskExecutor
并刻意去看2023、2024年的帖子,发现基本都说 默认的异步线程池 SimpleAsyncTaskExecutor 线程确实不可以重用。
感觉还是我本身项目代码的问题,我应该在某个地方设置了 最多可用线程数为8或者怎么样。

尝试1:既然注释没有效果,直接删掉 SchedulingConfig 这个类
发现还是一样的。

尝试2:去其他项目,运行这个demo
发现还是一样的 8 个线程在重用

则现在又有1种猜测
我加了 @Async 注解,到底有没有用 SimpleAsyncTaskExecutor 线程池

突然想起来了之前看 一个项目做的笔记。
说 @Async 注解的原理是

在这里插入图片描述
发现这里的 核心线程数确实是8 ,md破案了。
感觉根本没有用 SimpleAsyncTaskExecutor。

再去问 文心一言

当在Spring应用中使用@Async注解来声明异步方法时,如果没有明确指定线程池,那么默认情况下会使用SimpleAsyncTaskExecutor ,那如果是SpringBoot应用呢

在Spring Boot应用中,当使用@Async注解来声明异步方法时,如果没有明确指定线程池,其默认行为会根据Spring Boot的版本有所不同。

早期版本

在Spring Boot的某些早期版本中(通常指2.1之前的版本),如果没有特别配置,@Async注解标注的异步方法可能会默认使用SimpleAsyncTaskExecutor。这个执行器每次执行一个任务时都会创建一个新的线程,而不是复用现有的线程,这可能导致在高并发场景下创建大量线程,进而引发性能问题。

2.1及以后版本

从Spring Boot 2.1版本开始,情况发生了变化。在Spring Boot 2.1及以后的版本中,@Async注解默认使用的线程池不再是SimpleAsyncTaskExecutor,而是ThreadPoolTaskExecutor。这意味着,如果没有特别配置,异步方法将会在一个线程池中执行,该线程池具有核心线程数、最大线程数、队列容量等配置参数。

具体来说,ThreadPoolTaskExecutor的默认配置通常包括:

  • 核心线程数:8
  • 最大线程数Integer.MAX_VALUE
  • 队列容量Integer.MAX_VALUE(使用LinkedBlockingQueue
  • 空闲线程保留时间:60秒
  • 线程池拒绝策略AbortPolicy(当线程池饱和时,尝试执行新任务会抛出异常)

自定义配置

尽管Spring Boot提供了默认的线程池配置,但在实际应用中,根据业务需求和系统负载,这些默认配置可能需要调整。开发者可以通过在application.ymlapplication.properties文件中添加相应的配置属性来自定义线程池的行为。

例如,要调整核心线程数、最大线程数和队列容量,可以在application.properties中添加如下配置:

spring.task.execution.pool.core-size=10
spring.task.execution.pool.max-size=20
spring.task.execution.pool.queue-capacity=50

或者,在application.yml中添加类似配置:

spring:
  task:
    execution:
      pool:
        core-size: 10
        max-size: 20
        queue-capacity: 50

结论

因此,在Spring Boot应用中,如果没有明确指定线程池,@Async注解默认使用的线程池从Spring Boot 2.1版本开始是ThreadPoolTaskExecutor,而不是SimpleAsyncTaskExecutor。这提供了更好的线程复用和资源管理能力,但在实际应用中仍需要根据需要进行适当的配置调整。

再点进去

在这里插入图片描述

在这里插入图片描述

确实需要自定义异步线程池。

我之前的demo默认走的是 ThreadPoolTaskExecutor的配置。

但是这里我就好奇了,那在Spring 框架里面默认的线程池配置是通过哪个类实现的???

在这里插入图片描述

在这里插入图片描述

ai得出这个答案,我就不单独去验证Spring框架下面,@Scheduled 注解的行为了,毕竟现在都是基于SpringBoot开发。

如何自定义异步线程池,参考了 https://blog.csdn.net/kdzandlbj/article/details/139046266 的第二种方式。

在这里插入图片描述

异步线程池定义如图所示

在这里插入图片描述

启动项目,发现确实生效了,最大的执行线程就是5

在这里插入图片描述

但是这里有个问题,为什么 springBoot(这应该是SpringBoot决定的吧)的log打印,好像是打印 线程名.subtring(str.length-15)

在这里插入图片描述
稍等讨论这个问题,我想知道这个

executor.setThreadNamePrefix("AsyncThreadPool-t-");

线程工厂是如何把这个 线程名前缀加在线程上的.
首先要认知到一个问题,任何框架的线程池肯定都是基于JDK线程池的,而JDK线程池里面的线程都是由线程工厂创建的 也就是 ThreadFactory。所以,直接去找线程工厂。

在这里插入图片描述

在这里插入图片描述
点进去 CustomizableThreadFactory 看一看

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

再来说 SpringBoot输出的为什么是 substring的ThreadName。

在这里插入图片描述
找了一下感觉乱七八糟的,没什么章法,直接百度吧。

百度了一圈没有说什么源码的,都是说配置的,没劲,我自己debug看吧。

在这里插入图片描述

在这里插入图片描述

但是代码到下面就走完了。

在这里插入图片描述

事前先百度了一下。
在这里插入图片描述

ok,到这里 线程日志打印截断15个字符就结束了。


网站公告

今日签到

点亮在社区的每一天
去签到