1. 程式人生 > >spring任務執行器與任務排程器(TaskExecutor And TaskScheduler)

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); //同樣讓執行緒睡上一段時間,以便檢視效果
  
  測試中並未處理非同步執行後如果出現異常的情況。異常情況發生後如何處理之後再做記錄。