我們在日常開發中,經常跟多線程打交道,Spring 為我們提供了一個線程池方便我們開發,它就是 ThreadPoolTaskExecutor ,接下來我們就來聊聊 Spring 的線程池吧。
使用@Async聲明多線程
SpringBoot 提供了注解 @Async 來使用線程池, 具體使用方法如下:
- 在啟動類(配置類)添加@EnableAsync來開啟線程池
- 在需要開啟子線程的方法上添加注解 @Async
下面是一個簡單的例子:
@Component
@EnableAsync
@EnableScheduling
public 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 容器。
- 異步方法使用注解 @Async 的返回值只能為 void 或者 Future。
總結
上面簡單介紹了 Spring 自帶的線程池 ThreadPoolTaskExecutor 的配置和使用,并且講了線程池的參數和處理流程。當然Spring提供了7個線程池的實現,感興趣的可以自行了解~