Spring Boot中使用異步線程池、調度任務線程池的配置

轉自:https://www.cnblogs.com/slei212/p/10732260.htmlhttps://segmentfault.com/a/1190000012506685https://www.iteye.com/blog/zw7534313-2435135


從實現的技術上來分類,Java定時任務目前主要有三種:

  1. Java自帶的java.util.Timer類,這個類允許調度一個java.util.TimerTask任務。使用這種方式可以讓你的程序按照某一個頻度執行,但不能在指定時間運行;而且作業類需要集成java.util.TimerTask,一般用的較少。
  2. Quartz,這是一個功能比較強大的的調度器,可以讓你的程序在指定時間執行,也可以按照某一個頻度執行;使用起來需要繼承org.springframework.scheduling.quartz.QuartzJobBean,配置稍顯複雜,所以,一般會使用spring集成quartz,稍後會詳細介紹;
  3. Spring3.0以後自帶的task,即:spring schedule,可以將它看成一個輕量級的Quartz,而且使用起來比Quartz簡單許多。

有時項目中既需要異步任務, 也需要調度任務, 想把這兩個異步線程池分來就需要配置兩個線程池。
調度任務添加 @Scheduled 註解, 需要異步執行的方法添加 @Async 註解

中間遇到點小問題, 異步任務線程池總是不生效, 而是使用的調度任務線程池, 經過查文檔不斷嘗試解決了.
公司利用 slf4j 的 MDC 做鏈路跟蹤, 所以還需要添加前置操作, 使用 TaskDecorator 實現。

 

代碼如下:

AsyncConfig.java

package com.ecej.esmart.autodispatch.config;

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskDecorator;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.Map;
import java.util.concurrent.Executor;

@Slf4j
@EnableAsync
@Configuration
public class AsyncConfig implements AsyncConfigurer {

    @Bean
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("async-pool-");
        executor.setTaskDecorator(new MdcTaskDecorator());
        executor.setWaitForTasksToCompleteOnShutdown(true);
        executor.initialize();
        return executor;
    }

    class MdcTaskDecorator implements TaskDecorator {
        @Override
        public Runnable decorate(Runnable runnable) {
            Map<String, String> contextMap = MDC.getCopyOfContextMap();
            try {
                if (contextMap != null) {
                    MDC.setContextMap(contextMap);
                }
                runnable.run();
            } finally {
                /** 清理後會導致父線程的上下文清空,進入時會複製父線程的內容進行覆蓋,可不清理 */
                //MDC.clear();
            }
            return runnable;
        }
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return (throwable, method, params) -> {
            log.error("異步任務異常:方法:{} 參數:{}", method.getName(), JSON.toJSONString(params));
            log.error(throwable.getMessage(), throwable);
        };
    }
}

SchedulingConfig.java

package com.ecej.esmart.autodispatch.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;

@Slf4j
@Configuration
@EnableScheduling
public class SchedulingConfig implements SchedulingConfigurer {

    @Override
    public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
        scheduledTaskRegistrar.setTaskScheduler(taskScheduler());
    }

    @Bean(destroyMethod = "shutdown")
    public ThreadPoolTaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setPoolSize(5);
        scheduler.setThreadNamePrefix("dispatch-");
        scheduler.setAwaitTerminationSeconds(600);
        scheduler.setErrorHandler(throwable -> log.error("調度任務發生異常", throwable));
        scheduler.setWaitForTasksToCompleteOnShutdown(true);
        return scheduler;
    }
}

添加定時任務:

①串行方式:

  1. 在啓動類上加註解:@EnableScheduling即可實現。 
  2. 任務添加@Scheduled註解:@Scheduled接受兩種定時的設置,一種是cornexpression,一種是Rate/Delay表達式

②並行方式: 
爲了提高任務執行效率,可以採用並行方式執行定時任務,任務之間互不影響,只要實現SchedulingConfigurer接口就可以。 

@Configuration 
public class ScheduledConfig implements SchedulingConfigurer { 

    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { 
        taskRegistrar.setScheduler(setExecutor()); 
    } 

    @Bean(destroyMethod="shutdown") 
    public Executor setExecutor(){ 
        return Executors.newScheduledThreadPool(5); // 5個線程來處理。 
    } 
} 

SpringBoot 定時任務@Scheduled及SchedulingConfigurer

注:Spring 中,創建定時任務除了使用@Scheduled 註解外,還可以使用 SchedulingConfigurer。

@Schedule 註解有一個缺點,其定時的時間不能動態的改變,而基於 SchedulingConfigurer 接口的方式可以做到。SchedulingConfigurer 接口可以實現在@Configuration 類上,同時不要忘了,還需要@EnableScheduling 註解的支持。

1.基於註解實現方式

@Component
@EnableScheduling   //開啓定時任務
@EnableAsync   //開啓多線程
public class TimerJob {
   
    private Logger log = Logger.getLogger(TimerJob.class);
       @Async //異步方法;這些方法將在執行的時候,將會在獨立的線程中被執行,調用者無需等待它的完成,即可繼續其他的操作。
       @Scheduled(cron = "* 0/30 * * * ?")  //時間規則
    public  void matterTasks() {
            //業務邏輯
    }  
}         

2.SchedulingConfigurer實現方式  

2.1、在啓動類必須加上@EnableScheduling   //開啓定時任務

2.2、實現SchedulingConfigurer並重寫configureTasks方法

@Component  //實現SchedulingConfigurer並重寫configureTasks方法
public class OrderJobThread implements SchedulingConfigurer {

    private Logger log = LoggerFactory.getLogger(OrderJobThread.class);
    private String cron = "* 0/1 * * * ?"; //調用set方法可動態設置時間規則

    public String getCron() {
        return cron;
    }

    public void setCron(String cron) {
        this.cron = cron;
    }

    private String name = "測試"; //調用set方法可以動態設置日誌名

    public String getName() {
       return name;
    }

    public void setName(String name) {
        this.name = name;
    }
   
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.addCronTask(new Runnable() {     

            @Override
            public void run() {
                log.warn(name+" --- > 開始");
                                //業務邏輯
                                log.warn(name+" --- > 結束");
            }
        }, cron);  //加入時間
    }
}

2.3、設置項目啓動後,初始化 定時任務執行時間

有時希望項目在啓動的時候加載一些系統參數或者方法,就要用到ApplicationRunner

ApplicationRunner是一個接口,我們需要實現它,並重寫run()方法,當項目啓動時,run()方法便會自動執行

@Component
@Order(value = 1)  //value值會 從小至大的執行
public class TimmerStartController implements ApplicationRunner  {

    private static Logger logger = LoggerFactory.getLogger(TimmerStartController.class);

    @Autowired
    private OrderJobThread orderJobThread;  //得到定時任務

    @Override
    public void run(ApplicationArguments args) throws Exception {
        logger.info("=========== 項目啓動後,初始化 定時任務執行時間 =============");
        orderJobThread.setCron("* 0/5 * * * ?");  //根據需求重新賦值時間規則
        orderJobThread.setName("ordersTasks"); //賦值name
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章