文章系转载,方便整理和归纳,源文地址:https://zhuanlan.zhihu.com/p/346086161
前言
上一篇分享了JDK自带的线程池ThreadPoolExecutor的配置和参数详解,然而我们实际开发中更多的是使用SpringBoot来开发,Spring默认也是自带了一个线程池方便我们开发,它就是ThreadPoolTaskExecutor,接下来我们就来聊聊Spring的线程池吧。
Spring默认线程池simpleAsyncTaskExecutor
Spring异步线程池的接口类是TaskExecutor,本质还是java.util.concurrent.Executor,没有配置的情况下,默认使用的是simpleAsyncTaskExecutor。
@Async演示Spring默认的simpleAsyncTaskExecutor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29@Component @EnableAsync public class ScheduleTask { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @Async @Scheduled(fixedRate = 2000) public void testScheduleTask() { try { Thread.sleep(6000); System.out.println("Spring1自带的线程池" + Thread.currentThread().getName() + "-" + sdf.format(new Date())); } catch (InterruptedException e) { e.printStackTrace(); } } @Async @Scheduled(cron = "*/2 * * * * ?") public void testAsyn() { try { Thread.sleep(1000); System.out.println("Spring2自带的线程池" + Thread.currentThread().getName() + "-" + sdf.format(new Date())); } catch (Exception ex) { ex.printStackTrace(); } } }

从运行结果可以看出Spring默认的@Async用线程池名字为SimpleAsyncTaskExecutor,而且每次都会重新创建一个新的线程,所以可以看到TaskExecutor-后面带的数字会一直变大。
simpleAsyncTaskExecutor的特点是,每次执行任务时,它会重新启动一个新的线程,并允许开发者控制并发线程的最大数量(concurrencyLimit),从而起到一定的资源节流作用。默认是concurrencyLimit取值为-1,即不启用资源节流。
Spring的线程池ThreadPoolTaskExecutor
上面介绍了Spring默认的线程池simpleAsyncTaskExecutor,但是Spring更加推荐我们开发者使用ThreadPoolTaskExecutor类来创建线程池,其本质是对java.util.concurrent.ThreadPoolExecutor的包装。
这个类则是spring包下的,是Spring为我们开发者提供的线程池类,这里重点讲解这个类的用法。
Spring提供了xml给我们配置ThreadPoolTaskExecutor线程池,但是现在普遍都在用SpringBoot开发项目,所以直接上yaml或者properties配置即可,或者也可以使用@Configuration配置也行,下面演示配置和使用。
ThreadPoolTaskExecutor配置
application.properties
1
2
3
4
5
6
7
8
9
10
11# 核心线程池数 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=god-jiang-task-
AsyncScheduledTaskConfig.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41@Configuration public class AsyncScheduledTaskConfig { @Value("${spring.task.execution.pool.core-size}") private int corePoolSize; @Value("${spring.task.execution.pool.max-size}") private int maxPoolSize; @Value("${spring.task.execution.pool.queue-capacity}") private int queueCapacity; @Value("${spring.task.execution.thread-name-prefix}") private String namePrefix; @Value("${spring.task.execution.pool.keep-alive}") private int keepAliveSeconds; @Bean public Executor myAsync() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); //最大线程数 executor.setMaxPoolSize(maxPoolSize); //核心线程数 executor.setCorePoolSize(corePoolSize); //任务队列的大小 executor.setQueueCapacity(queueCapacity); //线程前缀名 executor.setThreadNamePrefix(namePrefix); //线程存活时间 executor.setKeepAliveSeconds(keepAliveSeconds); /** * 拒绝处理策略 * CallerRunsPolicy():交由调用方线程运行,比如 main 线程。 * AbortPolicy():直接抛出异常。 * DiscardPolicy():直接丢弃。 * DiscardOldestPolicy():丢弃队列中最老的任务。 */ executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy()); //线程初始化 executor.initialize(); return executor; } }
在方法上添加@Async注解,然后还需要在@SpringBootApplication启动类或者@Configuration注解类上 添加注解@EnableAsync启动多线程注解,@Async就会对标注的方法开启异步多线程调用,注意,这个方法的类一定要交给Spring容器来管理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29@Component @EnableAsync public class ScheduleTask { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @Async("myAsync") @Scheduled(fixedRate = 2000) public void testScheduleTask() { try { Thread.sleep(6000); System.out.println("Spring1自带的线程池" + Thread.currentThread().getName() + "-" + sdf.format(new Date())); } catch (InterruptedException e) { e.printStackTrace(); } } @Async("myAsync") @Scheduled(cron = "*/2 * * * * ?") public void testAsyn() { try { Thread.sleep(1000); System.out.println("Spring2自带的线程池" + Thread.currentThread().getName() + "-" + sdf.format(new Date())); } catch (Exception ex) { ex.printStackTrace(); } } }

以上从运行结果可以看出,自定义ThreadPoolTaskExecutor可以实现线程的复用,而且还能控制好线程数,写出更好的多线程并发程序。
另外需要注意的是:关于注解失效需要注意以下几点
- 注解的方法必须是public方法
- 方法一定要从另一个类中调用,也就是从类的外部调用,类的内部调用是无效的,因为@Transactional和@Async注解的实现都是基于Spring的AOP,而AOP的实现是基于动态代理模式实现的。那么注解失效的原因就很明显了,有可能因为调用方法的是对象本身而不是代理对象,因为没有经过Spring容器。
- 异步方法使用注解@Async的返回值只能为void或者Future
上面注解中已经注释了参数的详解,这里重点讲解一下Spring线程池的拒绝策略和处理流程。
拒绝策略
rejectedExectutionHandler参数字段用于配置绝策略,常用拒绝策略如下
- AbortPolicy:用于被拒绝任务的处理程序,它将抛出RejectedExecutionException
- CallerRunsPolicy:用于被拒绝任务的处理程序,它直接在execute方法的调用线程中运行被拒绝的任务。
- DiscardOldestPolicy:用于被拒绝任务的处理程序,它放弃最旧的未处理请求,然后重试execute。
- DiscardPolicy:用于被拒绝任务的处理程序,默认情况下它将丢弃被拒绝的任务。
处理流程
- 查看核心线程池是否已满,不满就创建一条线程执行任务,否则执行第二步。
- 查看任务队列是否已满,不满就将任务存储在任务队列中,否则执行第三步。
- 查看线程池是否已满,即就是是否达到最大线程池数,不满就创建一条线程执行任务,否则就按照策略处理无法执行的任务。

总结
这次分享了Spring自带的线程池ThreadPoolTaskExecutor的配置和使用,并且讲了线程池的参数和处理流程。当然Spring提供了7个线程池的实现,感兴趣的可以自行了解~~~
希望以上的分享对你们有所帮助,喜欢的点个赞支持一下我吧~~~
最后
以上就是繁荣眼神最近收集整理的关于Spring自带的线程池ThreadPoolTaskExecutor的全部内容,更多相关Spring自带内容请搜索靠谱客的其他文章。
发表评论 取消回复