我们在日常开发中,经常跟多线程打交道,Spring 为我们提供了一个线程池方便我们开发,它就是 ThreadPoolTaskExecutor,接下来我们就来聊聊 Spring 的线程池吧 。
使用@Async声明多线程SpringBoot 提供了注解 @Async 来使用线程池, 具体使用方法如下:
- 在启动类(配置类)添加@EnableAsync来开启线程池
- 在需要开启子线程的方法上添加注解 @Async
@Component@EnableAsync@EnableSchedulingpublic class ScheduleTask {@Async@Scheduled(fixedRate = 2000)public void testAsync1() {try {Thread.sleep(6000);System.out.println(LocalDateTime.now() + "--线程1:" + Thread.currentThread().getName());} catch (InterruptedException e) {e.printStackTrace();}}@Async@Scheduled(cron = "*/2 * * * * ?")public void testAsync2() {try {Thread.sleep(1000);System.out.println(LocalDateTime.now() + "--线程2:" + Thread.currentThread().getName());} catch (Exception ex) {ex.printStackTrace();}}}
启动项目,得到如下日志结果:文章插图
图片
可以发现在当前环境下 task-${id} 这个 id 并不是一直增长的,而是一直在复用 1-8 。这个时候可能就会有的小伙伴们会比较好奇,默认的不是 SimpleAsyncTaskExecutor 吗?为什么从日志打印的效果上看像是一直在复用 8 个线程,难道用的是 ThreadPoolTaskExecutor?
原因是 SpringBoot2.1.0 版本后,新增了 TaskExecutionAutoConfiguration 配置类 。其中声明的默认线程池就是 ThreadPoolTaskExecutor。而 @Async 在选择执行器的时候会先去 IOC 容器中先找是否有 TaskExecutor 的 Bean对象,所以在当前版本 SpringBoot 中,@Async 的默认 TaskExecutor 是 ThreadPoolTaskExecutor 。
线程池配置在 SpringBoot 项目中 , 我们可以在 yaml 或者 properties 配置文件中配置,或者使用 @Configuration 配置,下面演示配置方法 。
- Application.properties配置文件中配置
# 核心线程池数spring.task.execution.pool.core-size=5# 最大线程池数spring.task.execution.pool.max-size=10# 任务队列的容量spring.task.execution.pool.queue-capacity=5# 非核心线程的存活时间spring.task.execution.pool.keep-alive=60# 线程池的前缀名称spring.task.execution.thread-name-prefix=test-task-
- 配置类中配置
@Bean(name = "myThreadPoolTaskExecutor")public ThreadPoolTaskExecutor getMyThreadPoolTaskExecutor() {ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();int i = Runtime.getRuntime().avAIlableProcessors();taskExecutor.setCorePoolSize(i * 2);taskExecutor.setMaxPoolSize(i * 2);taskExecutor.setQueueCapacity(i * 2 * 100);taskExecutor.setKeepAliveSeconds(60);taskExecutor.setThreadNamePrefix("my-task-");taskExecutor.initialize();return taskExecutor;}
拒绝策略RejectedExectutionHandler 参数字段用于配置绝策略,常用拒绝策略如下- AbortPolicy:用于被拒绝任务的处理程序 , 它将抛出RejectedExecutionException
- CallerRunsPolicy:用于被拒绝任务的处理程序,它直接在execute方法的调用线程中运行被拒绝的任务 。
- DiscardOldestPolicy:用于被拒绝任务的处理程序 , 它放弃最旧的未处理请求,然后重试execute 。
- DiscardPolicy:用于被拒绝任务的处理程序,默认情况下它将丢弃被拒绝的任务 。
- 查看核心线程池是否已满,不满就创建一条线程执行任务,否则执行第2步 。
- 查看任务队列是否已满,不满就将任务存储在任务队列中,否则执行第3步 。
- 查看线程池是否已满,即就是是否达到最大线程池数,不满就创建一条线程执行任务 , 否则就按照策略处理无法执行的任务 。
- 注解的方法必须是 public 方法 。
- 方法一定要从另一个类中调用,也就是从类的外部调用 , 类的内部调用是无效的,因为 @Transactional 和 @Async 注解的实现都是基于 Spring 的 AOP,而 AOP 的实现是基于动态代理模式实现的 。那么注解失效的原因就很明显了,有可能因为调用方法的是对象本身而不是代理对象,因为没有经过 Spring 容器 。
推荐阅读
- SpringBootCms
- 通过线程池方式改造Stream.parallel并行流
- 程序员必会之最详细的ThreadPoolExecutor 线程池七大参数含义
- 并发编程的艺术-“程”:探索进程、线程、协程、纤程与管程
- pdf 解密方法 快来看看吧
- 解密前端三巨头:HTML、CSS和JavaScript的关系
- 为什么Java官方不推荐池化虚拟线程?
- Java面试题之SpringBoot 框架
- SpringBoot中ApplicationEvent详解
- Java 21:下一个LTS版本,提供了虚拟线程、记录模式和模式匹配