spring任務執行器與任務排程器(TaskExecutor And TaskScheduler)
對於多執行緒及週期性排程相關的操作,spring框架提供了TaskExecutor和TaskScheduler介面為非同步執行和任務排程。並提供了相關實現類給開發者使用。(只記錄採用註解的使用形式,對於XML的使用形式不做筆記。)
Spring官方對TaskExecutor的相關解釋:
Spring的TaskExecutor介面與java.util.concurrent.Executor介面相同。該介面具有單個方法(execute(Runnable task)),該方法根據執行緒池的語義和配置接受要執行的任務。
二話(寫)不說,自說(寫)自話(字)直接拐上了
TaskExecutor介面相關實現類
實現類名 對應解釋(直接甩翻譯了)
SyncTaskExecutor 該實現類不會執行非同步呼叫。 相反,每次呼叫都在呼叫的執行緒中進行(翻譯過來也即同步任務執行器)。 它主要用於不需要多執行緒的情況,例如在簡單的測試用例中。
SimpleAsyncTaskExecutor 此實現不會重用任何執行緒。 相反,它為每次呼叫啟動一個新執行緒。 但是,它確實支援併發限制,該限制會阻止超出限制的任何呼叫,直到釋放插槽為止。(說簡單了,就是要使用了直接建立一個執行緒)
ConcurrentTaskExecutor 此實現是java.util.concurrent.Executor例項的介面卡。很少需要直接使用ConcurrentTaskExecutor**(官網自己都覺得很少使用,不過相對於ThreadPoolTaskExecutor,官網推薦如果ThreadPoolTaskExecutor不夠靈活,無法滿足需求,則可以使用ConcurrentTaskExecutor)**。
ThreadPoolTaskExecutor 殺手鐗級的任務排程器(最常用),可以說已經足夠滿足我們的需求了(除非,非常非常特例才使用ConcurrentTaskExecutor)。官網翻譯重要片段:公開了bean屬性,用於配置java.util.concurrent.ThreadPoolExecutor並將其包裝在TaskExecutor中
WorkManagerTaskExecutor 此實現使用CommonJ WorkManager作為其後備服務提供程式,並且是在Spring應用程式上下文中在WebLogic或WebSphere上設定基於CommonJ的執行緒池整合的中心便利類。
DefaultManagedTaskExecutor 此實現在JSR-236相容的執行時環境(例如Java EE 7+應用程式伺服器)中使用JNDI獲取的ManagedExecutorService,為此目的替換CommonJ WorkManager。(說明了就是依賴環境)
其中可能今後工作中會用到的(包括測試) SyncTaskExecutor、SimpleAsyncTaskExecutor、ConcurrentTaskExecutor、ThreadPoolTaskExecutor此四個實現類。重點關注ThreadPoolTaskExecutor此類。
記:以上均實現了spring提供的TaskExecutor**介面。
Spring官方對TaskSheduler介面的相關解釋:
用於在將來的某個時間點排程任務。
TaskScheduler介面相關實現類
實現類名 對應解釋
ConcurrentTaskScheduler 該類實際繼承了ConcurrentTaskExecutor物件。只是實現了TaskScheduler介面,增加了相關定時排程任務的方法。
ThreadPoolTaskScheduler spring對該類設計原則同ThreadPoolTaskExecutor類。是為了定時排程任務不依賴相關的執行容器(例如weblogic、WebSphere等)。其底層委託給ScheduledExecutorService,向外暴露相關的常見bean配置屬性。
接下來對以上相應的實現類通過註解的形式進行相應的測試
首先進行解釋要用到的幾個註解,對這幾個註解總結如下表
註解名 解釋
@EnableAsync 開啟非同步執行。官方文件中解釋:該註解新增到@Configuration標註的類上以開始非同步執行。開啟後@Async標註的方法或類即可非同步執行。
@EnableScheduling 開啟定時排程。官方文件解釋也是配合@Configuration一起使用。開啟後@Scheduled註解標註的方法即可自動定時(或延遲)執行。
@Async 非同步執行註解。可標註類和方法。標註類時,則該類下所有方法均可使用非同步執行。標註方法時,則該方法可使用非同步執行。當標註有@Configuration註解的配置類上標註了@EnableAsync註解後即可生效。
@Scheduled 標註相關方法後,如果配置類標註了@EnableScheduling後即可開啟定時排程任務。
接下來先測試非同步執行器(TaskExecutor)
使用方式1(直接使用spring容器中的ThreadPoolTaskExecutor):
先通過@Bean註解將ThreadPoolTaskExecutor放入Spring容器中:
@Configuration
public class ExecutorBean {
/**
* 執行器ThreadPoolTaskExecutor
*/
@Bean
public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10); // 設定核心執行緒池大小(這裡設定為初始化10個)
executor.setMaxPoolSize(30); // 設定最大執行緒池大小(當核心執行緒池不夠用時候,會自動在原基礎上增加。最大為30個)
executor.setQueueCapacity(2000); // 設定佇列容量為2000個。
return executor;
然後在標註有@Configuration註解的配置類或SpringBoot啟動類上新增@EntityAsync註解。這裡在標有@SpringBootApplication註解的SpringBoot啟動類的入口方法上新增@EntityAsync註解。
@SpringBootApplication
@EnableAsync
public class ScheduleApplication {
public static void main(String[www.tongqt178.com] args) {
SpringApplication.run(ScheduleApplication.class, args);
然後在需要採取非同步執行的方法上(或類上,此測試使用方法)標註@Async註解:
@Async(value="threadPoolTaskExecutor")
public void testThreadPoolTaskExecutor() {
System.err.println("--- testThreadPoolTaskExecutor --");
測試用例:
@Test
public void testThreadPoolTaskExecutor() {
System.err.println("--- 開始 ---");
taskExecutorService.testThreadPoolTaskExecutor();
System.err.println("--- 結束 ---");
執行結果:
--- 開始 ---
--- 結束 ---
--- testThreadPoolTaskExecutor --
使用方式2(通過修改預設的執行緒池配置,即實現AsyncConfigurer介面,並重寫其中的getAsyncExecutor方法[因為是JDK8提供的default方法,所以才稱為重寫]):
首先,先實現AsyncConfigurer介面,重寫getAsyncExecutor方法並將此實現類作為配置類裝載進spring容器中(記:對於void返回型別,異常未被捕獲且無法傳輸,所以getAsyncUncaughtExceptionHandler方法用於處理非同步呼叫後出現異常的情況。這裡僅僅記錄未出現異常的測試),同時新增@EnableAsync開啟可非同步呼叫(也可以在springBoot啟動類中的入口方法上新增)。
@Configuration
@EnableAsync
public class CustomAsyncConfigurer implements AsyncConfigurer {
/**
* 設定執行緒池相關的配置
* @return ThreadPoolTaskExecutor
*/
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10); // 設定核心執行緒池大小(這裡設定為初始化10個)
executor.setMaxPoolSize(30); // 設定最大執行緒池大小(當核心執行緒池不夠用時候,會自動在原基礎上增加。最大為30個)
executor.setQueueCapacity(2000); // 設定佇列容量為2000個。
executor.initialize();
return executor;
然後可以直接使用@Async註解標註需要非同步執行的方法(或類上,此測試使用方法)
@Async
public void testThreadPoolTaskExecutor(Long id) {
System.err.println("--- testThreadPool www.mingheyl178.com/ TaskExecutor --" + id);
測試用例:
@Test
public void testThreadPoolTaskExecutor() {
System.err.println("--- 開始前 ---");
System.err.println("--- 開始 ---");
for(int i =0;i<20;i++) {
taskExecutorService.testThreadPoolTaskExecutor(new Long(i));
}
System.err.println("--- 結束 ---");
System.err.println("--- 結束後 ---");
--- 開始前 ---
--- 開始 ---
--- testThreadPoolTaskExecutor --0
--- testThreadPoolTaskExecutor --1
--- testThreadPoolTaskExecutor --2
--- testThreadPoolTaskExecutor --3
--- testThreadPoolTaskExecutor --4
--- testThreadPoolTaskExecutor --5
--- testThreadPoolTaskExecutor --6
--- testThreadPoolTaskExecutor --7
--- testThreadPoolTaskExecutor --8
--- testThreadPoolTaskExecutor --9
--- testThreadPoolTaskExecutor --10
--- testThreadPoolTaskExecutor --11
--- testThreadPoolTaskExecutor --12
--- testThreadPoolTaskExecutor --13
--- testThreadPoolTaskExecutor --14
--- testThreadPoolTaskExecutor --17
--- testThreadPoolTaskExecutor --16
--- testThreadPoolTaskExecutor --15
--- 結束 ---
--- 結束後 ---
--- testThreadPoolTaskExecutor --19
--- testThreadPoolTaskExecutor --18
**使用方式3:因為ThreadPoolTaskScheduler實現了TaskExecutor相關的介面。所以同樣可以用ThreadPoolTaskScheduler替換ThreadPoolTaskExecutor來呼叫非同步執行器。
同樣實現AsyncConfigurer介面,重寫getAsyncExecutor方法並將此實現類作為配置類裝載進spring容器中。只是此時用ThreadPoolTaskScheduler替換ThreadPoolTaskExecutor類作為任務執行器
@Configuration
@EnableAsync
public class CustomAsyncConfigurer implements AsyncConfigurer {
/**
* 設定執行緒池相關的配置
* @return ThreadPoolTaskExecutor
*/
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskScheduler executor = new ThreadPoolTaskScheduler();
executor.setPoolSize(30);
executor.initialize();
return executor;
然後用@Asnyc標註方法
@Async
public void testThreadPoolTaskScheduler(Long id) {
System.err.println("--- testThreadPoolTaskExecutor --" + id);
@Test
public void testThreadPoolTaskScheduler() {
System.err.println("--- 開始前 ---");
System.err.println("--- 開始 ---");
for(int i =0;i<20;i++) {
taskExecutorService.testThreadPoolTaskScheduler(new Long(i));
}
System.err.println("--- 結束 ---");
System.err.println("--- 結束後 ---");
經過測試,用例3沒啥效果...測試方式可能有誤。同樣的非同步呼叫ThreadPoolTaskScheduler類可能不能當做ThreadPoolTaskExecutor 類使用。雖然同樣實現了TaskExecutor介面(看來得看底層原始碼了,現在僅僅記錄下來先)
現在來測試定時排程器(TaskScheduler)
使用方式1(直接使用@EnableScheduling開啟定時排程任務,然後對需要定時排程的方法用@Sheduled註解標註):
直接在SpringBoot啟動器類中的入口方法上標註或標註了@Configuration註解的配置類上使用@EnableScheduling註解
//在啟動類上新增
@SpringBootApplication
@EnableScheduling
public class ScheduleApplication {
public static void main(String[] args) {
SpringApplication.run(ScheduleApplication.class, args);
}
}
//或在在配置類上標註
@Configuration
@EnableScheduling
public class CustomScheduleConfigurer implements AsyncConfigurer {
需要在定時任務的方法上新增@Scheduled
@Scheduled(fixedRate = 300) //表示每300毫秒呼叫一次該方法
@Override
public void threadPoolTaskScheduler() {
System.err.println("--- threadPoolTaskScheduler --");
測試用例
@Test
public void schedulers() throws Exception {
Thread.sleep(50000000); //讓執行緒睡上一段時間,以便檢視效果
因為應用一啟動後定時排程器便會開始執行。如果測試用例不使用執行緒睡眠的話程式會一瞬間執行結束,有可能看不到效果。
記:@Scheduled註解支援Quartz的CRON表示式來規劃定時任務(僅做記錄:SPRING官網示例)。
spring框架支援Quartz來使用定時排程任務。
使用方式2(實現SchedulingConfigurer配置類,規定定時任務)
首先實現SchedulingConfigurer配置類,並啟用定時排程任務
@Configuration
@EnableScheduling
public class CustomSchedulerConfigurer implements SchedulingConfigurer {
@Override
public void configureTasks(www.michenggw.com ScheduledTaskRegistrar taskRegistrar) {
IntervalTask task = new IntervalTask(new Runnable() {
@Override
public void run() {
System.err.println("----間隔執行----");
}
}, 2000);
taskRegistrar.addFixedDelayTask(task); //taskRegistrar有更多相關方法來執行定時排程任務。測試用例先做簡單記錄
然後進行測試
@Test
public void schedulers() throws Exception {
Thread.sleep(50000000); //同樣讓執行緒睡上一段時間,以便檢視效果
測試中並未處理非同步執行後如果出現異常的情況。異常情況發生後如何處理之後再做記錄。