天天看點

SpringBoot定時任務和異步操作SpringBoot定時任務和異步操作

SpringBoot定時任務和異步操作

日常求贊,感謝老闆。

歡迎關注公衆号:其實是白羊。幹貨持續更新中…

SpringBoot定時任務和異步操作SpringBoot定時任務和異步操作

一、定時任務

在做業務時總會有這樣的場景:在特定時間去執行某些邏輯。這其實就是定時任務的應用場景,比如:需要每月一日給使用者發上月資料總結等場景。

1.技術

實作定時任務的技術很多

  • Timer:JDK自帶的java.util.Timer其實更類似于定時器,可實作延遲執行和按照一定頻率執行,也可以指定某個時間執行,使用較少
  • ScheduledExecutorService:也是JDK自帶的,是基于線程池設計的定時任務類,根據Executors建立時的線程數量去執行具體任務(多個線程數量就是每個人物配置設定一個線程)
  • Spring Task:即今天要介紹了主角,是Spring自帶的,當然這裡通過Springboot使用。
  • Quartz:開源的排程架構,功能更加強大

2.注解使用

1)@EnableScheduling

開啟定時任務,可标注在啟動類或者任何配置類上(能掃描到對象上都可以)

2)@Scheduled

配置具體的任務執行規則,可标注在能被掃描的類的方法上,屬性有:

  • fixedRate:上一個任務開始時間和下一個任務開始時間的時間間隔(毫秒)
  • fixedDelay:上一個任務的結束時間和下一個任務的開始時間的時間間隔(毫秒)
  • initialDelay:第一次執行延遲執行的時間(毫秒)
  • cron:通過cron表達式來配置執行時機

3.cron表達式

定時任務的場景:延遲執行、一定頻率執行、指定時間執行

這裡的cron表達式即為了描述任務執行的時間規則。cron由6-7個元素組成,他們之間使用空格來分割,以此代表:

  • 秒:0~59
  • 分:0~59
  • 時:0~23
  • 日:1~31(具體月的最後一天也可能是30)
  • 月:1~12
  • 星期:1~7(注意1為周日)
  • 年:1970~2099

除了上面的數字元素值還有下面幾個特殊的元素值:

  • *:表示任意一個值都會觸發,七個元素位置中都可以出現,如在分鐘出現即表示每分鐘都回去執行
  • ?:和*類似,但隻會出現在日和星期(這兩個位置隻能有一個是具有意義的描述,如:要麼是每個月的3号要麼是每個月的每個星期的周三),當其中一個配置了有意義的值,另一個寫?
  • -:表示範圍(至),七個元素位置中都可以出現,如分鐘裡出現1-7則表示1分鐘到7分鐘每分鐘執行一次
  • /:表示從開始時間每隔多長時間執行一次,七個元素位置中都可以出現,如分鐘裡出現1/7則表示1分觸發一次1+7=8分觸發一次
  • ,:表示枚舉值,七個元素位置中都可以出現,如分鐘裡出現1,7則表示1分和7分各執行一次
  • L:表示最後,隻會出現在日和星期位置,日表示最後一天,星期則會搭配數字如5L表示最後一個周四
  • W:表示有效工作日(周一到周五),隻會出現在日期裡,前面會搭配數字,如5W:如果5号是周六那麼執行時間為周五即4号;如果5号是周日那麼執行時間為下周一即6号;(即系統會推薦離今天最近的一個工作日,但注意不會進行跨越查找:如果是31W且31号是周日,那麼會在29号執行)
  • LW:連用表示最後一個工作日,隻會出現在日期裡
  • #:(前後要加數字)表示第幾個星期幾,隻會出現在星期裡,如4#2 即表示第二個周三(前面表示周幾,後面表示第幾個)

了解了上面的表達式規則就可以寫出滿足條件的cron表達式了,建議多設計幾個場景練習下

4.單線程和多線程執行

如果就按上面的配置寫好任務A(fixedRate=2000)和任務B(fixedRate=3000)直接啟動執行的話,會存在以下問題:

  • 如果A1任務執行時間超過2s那麼原定于A1任務開始時2s後執行的任務A2就不能按時執行
  • 任務A和任務B要交替執行

上面的問題都不能讓我們的任務按照各自規定的執行計劃去執行,歸根到底還是所有的任務都在一個線程裡進行,是以做不到異步的并發執行,那這是就可以通過多線程來實作各個任務之間異步的并發執行

這裡我們可以使用Spring的@Async注解來實作異步

二、異步

SpringBoot同樣支援@Async來實作異步(增加了自動配置可直接使用)

1.注解

1)@EnableAsync

标注在啟動類或配置類上,表是開啟異步

2)@Async

可标注在類上(能被spring容器掃描到的類上)或方法上

标注在類上則這個類裡的方法都被表示為異步

2.異步方法兩種傳回值

  1. 不需要傳回值:void
  2. 需要傳回值:
    @Async  
        public Future<String> doTaskOne() throws Exception {
            return new AsyncResult<>("任務一完成");  
        }  
               

3.自動配置

使用@Async來實作異步,其底層還是使用了多線程來進行實作的,那麼這個多線程或者線程池是在哪裡設定或配置的呢?我們都直到SpringBoot加入了大量的自動配置,我們在spring-boot-autoconfigure包下面可以找到task包下的TaskExecutionAutoConfiguration類中:

@Lazy
@Bean(name = { APPLICATION_TASK_EXECUTOR_BEAN_NAME,
			AsyncAnnotationBeanPostProcessor.DEFAULT_TASK_EXECUTOR_BEAN_NAME })
@ConditionalOnMissingBean(Executor.class)
//這個注解的意思:當在容器中沒有發現Executor這個類則會加載這個bean,可以了解為此處為預設預設對象
//傳回的是一個spring為我們提供的線程池
	public ThreadPoolTaskExecutor applicationTaskExecutor(TaskExecutorBuilder builder) {
		return builder.build();
	}
           

找到TaskExecutorBuilder:

@Bean
	@ConditionalOnMissingBean
	public TaskExecutorBuilder taskExecutorBuilder() {
		TaskExecutionProperties.Pool pool = this.properties.getPool();
		TaskExecutorBuilder builder = new TaskExecutorBuilder();
		builder = builder.queueCapacity(pool.getQueueCapacity());
		builder = builder.corePoolSize(pool.getCoreSize());
		builder = builder.maxPoolSize(pool.getMaxSize());
		builder = builder.allowCoreThreadTimeOut(pool.isAllowCoreThreadTimeout());
		builder = builder.keepAlive(pool.getKeepAlive());
		builder = builder.threadNamePrefix(this.properties.getThreadNamePrefix());
		builder = builder.customizers(this.taskExecutorCustomizers.orderedStream()::iterator);
		builder = builder.taskDecorator(this.taskDecorator.getIfUnique());
		return builder;
	}
           

這裡的properties是在TaskExecutionProperties加載進來的,預設的參數:

private int queueCapacity = Integer.MAX_VALUE;
private int coreSize = 8; 
private int maxSize = Integer.MAX_VALUE;
private boolean allowCoreThreadTimeout = true;
           

根據下面可知,也可以從yml/properties檔案中自定義配置

除了上面的方法我們還可以定義自己的Bean來替換預設預設Bean,根據@ConditionalOnMissingBean(Executor.class)可知,我們隻需要在配置類裡加上:

@Bean
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(核心線程數量);
        executor.setMaxPoolSize(最大線程數量);
        executor.setQueueCapacity(任務隊列大小);
        executor.initialize();
        return executor;
    }
           

三、最後

根據上面的介紹,要實作異步的定時任務就可以展開,任務方法上面加上@Async來實作

點個贊啊親

如果你認為本文對你有幫助,可以「在看/轉發/贊/star」,多謝

如果你還發現了更好或不同的想法,還可以在留言區一起探讨下

歡迎關注公衆号:「其實是白羊」幹貨持續更新中…

SpringBoot定時任務和異步操作SpringBoot定時任務和異步操作
SpringBoot定時任務和異步操作SpringBoot定時任務和異步操作

繼續閱讀