SpringBoot中的定時任務的同步與異步

定時任務調度功能在咱們的開發中是很是常見的,隨便舉幾個例子:定時清除一些過時的數據,定時發送郵件等等,實現定時任務調度的方式也十分多樣,本篇文章主要學習各類實現定時任務調度方式的優缺點,以便爲往後選擇的時候提供必定的參考。java

本篇要點

  • 介紹Timer實現定時任務。git

  • 介紹ScheduledExecutorService實現定時任務。spring

  • 介紹SpringBoot使用SpringTask實現定時任務。springboot

  • 介紹SpringBoot使用SpringTask實現異步任務。多線程

Timer實現定時任務

基於JDK自帶的java.util.Timer,經過調度java.util.TimeTask讓某一段程序按某一固定間隔,在某一延時以後定時執行。異步

缺點:async

  1. 沒法指定某一時間的時候執行。
  2. 存在潛在bug,Timer運行多個TimeTask時,只要其中之一沒有捕獲拋出的異常,其它任務便會自動終止運行。
public class DemoTimer {

    //延時時間
    private static final long DELAY = 3000;

    //間隔時間
    private static final long PERIOD = 5000;

    public static void main(String[] args) {
        // 定義要執行的任務
        TimerTask task = new TimerTask() {
            @Override
            public void run() {
                System.out.println("任務執行 --> " + LocalDateTime.now());
            }
        };
        Timer timer = new Timer();
        timer.schedule(task, DELAY, PERIOD);
    }
}

ScheduledExecutorService實現定時任務

阿里巴巴開發規範明確規定:但願開發者使用ScheduledExecutorService代替Timer。ide

多線程並行處理定時任務時,Timer運行多個TimeTask時,只要其中之一沒有捕獲拋出的異常,其它任務便會自動終止運行,使用ScheduledExecutorService則沒有這個問題。spring-boot

public class DemoScheduledExecutorService {

    //延時時間
    private static final long DELAY = 3000;

    //間隔時間
    private static final long PERIOD = 5000;

    public static void main(String[] args) {

        Runnable task = new Runnable() {
            @Override
            public void run() {
                System.out.println("任務執行 --> " + LocalDateTime.now());
            }
        };

        ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
        service.scheduleAtFixedRate(task, DELAY, PERIOD, TimeUnit.MILLISECONDS);

    }
}

SpringBoot使用Spring Task實現定時任務

自動配置實現原理

Spring爲咱們提供了異步執行任務調度的方式,提供TaskExecutor,TaskScheduler接口,而SpringBoot的自動配置類org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration爲咱們默認注入了他們的實現:ThreadPoolTaskScheduler,本質上是ScheduledExecutorService 的封裝,加強在調度時間上的功能。學習

@ConditionalOnClass(ThreadPoolTaskScheduler.class)
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(TaskSchedulingProperties.class)
@AutoConfigureAfter(TaskExecutionAutoConfiguration.class)
public class TaskSchedulingAutoConfiguration {

	@Bean
	@ConditionalOnBean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
	@ConditionalOnMissingBean({ SchedulingConfigurer.class, TaskScheduler.class, ScheduledExecutorService.class })
	public ThreadPoolTaskScheduler taskScheduler(TaskSchedulerBuilder builder) {
		return builder.build();
	}

	@Bean
	@ConditionalOnMissingBean
	public TaskSchedulerBuilder taskSchedulerBuilder(TaskSchedulingProperties properties,
			ObjectProvider<TaskSchedulerCustomizer> taskSchedulerCustomizers) {
		TaskSchedulerBuilder builder = new TaskSchedulerBuilder();
		builder = builder.poolSize(properties.getPool().getSize());
		Shutdown shutdown = properties.getShutdown();
		builder = builder.awaitTermination(shutdown.isAwaitTermination());
		builder = builder.awaitTerminationPeriod(shutdown.getAwaitTerminationPeriod());
		builder = builder.threadNamePrefix(properties.getThreadNamePrefix());
		builder = builder.customizers(taskSchedulerCustomizers);
		return builder;
	}

}

新建工程,引入依賴

Spring Task是Spring Framework中的模塊,咱們只需引入spring-boot-starter依賴就能夠了。

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

編寫配置類@EnableScheduling

@Configuration
@EnableScheduling
public class ScheduleConfig {

}
  • @Configuration代表這是個配置類。
  • @EnableScheduling代表啓用Spring的定時任務調度功能。

定義定時任務@Scheduled

@Component
@Slf4j
public class DemoTask {

    private final AtomicInteger counts = new AtomicInteger();

    @Scheduled(cron = "0/5 * * * * *")
    public void execute() {
        log.info("[定時任務第 {} 次執行]", counts.incrementAndGet());
    }
}
  • @Component代表該類須要被掃描,以便於Spring容器管理。
  • @Scheduled標註須要調度執行的方法,定義執行規則,其必須指定cronfixedDelayfixedRate三個屬性其中一個。
    • cron:定義Spring cron表達式,網上有在線cron生成器,能夠對照着編寫符合需求的定時任務。
    • fixedDelay :固定執行間隔,單位:毫秒。注意,以調用完成時刻爲開始計時時間。
    • fixedRate :固定執行間隔,單位:毫秒。注意,以調用開始時刻爲開始計時時間。

主啓動類

@SpringBootApplication
public class SpringBootTaskApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootTaskApplication.class, args);
    }
}

定義配置文件

Spring Task 調度任務的配置,對應 TaskSchedulingProperties 配置類。SpringBoot容許咱們在yml或properties定製這些外部化配置,若是不配置也是沒有關係的,自動配置已經給你一套默認的值了。

spring:
  task:
    scheduling:
      thread-name-prefix: summerday- # 線程池的線程名的前綴。默認爲 scheduling- ,建議根據本身應用來設置
      pool:
        size: 10 # 線程池大小。默認爲 1 ,根據本身應用來設置
      shutdown:
        await-termination: true # 應用關閉時,是否等待定時任務執行完成。默認爲 false ,建議設置爲 true
        await-termination-period: 60 # 等待任務完成的最大時長,單位爲秒。默認爲 0 ,根據本身應用來設置

啓動項目測試

# 初始化一個 ThreadPoolTaskScheduler 任務調度器
2020-11-30 23:04:51.886  INFO 10936 --- [  restartedMain] o.s.s.c.ThreadPoolTaskScheduler          : Initializing ExecutorService 'taskScheduler'
# 每5s執行一次定時任務
2020-11-30 23:04:55.002  INFO 10936 --- [    summerday-1] com.hyh.task.DemoTask                    : [定時任務第 1 次執行]
2020-11-30 23:05:00.002  INFO 10936 --- [    summerday-1] com.hyh.task.DemoTask                    : [定時任務第 2 次執行]
2020-11-30 23:05:05.002  INFO 10936 --- [    summerday-2] com.hyh.task.DemoTask                    : [定時任務第 3 次執行]
2020-11-30 23:05:10.001  INFO 10936 --- [    summerday-1] com.hyh.task.DemoTask                    : [定時任務第 4 次執行]
2020-11-30 23:05:15.002  INFO 10936 --- [    summerday-3] com.hyh.task.DemoTask                    : [定時任務第 5 次執行]

SpringTask異步任務

SpringTask除了@Scheduled、@EnableScheduling同步定時任務以外,還有@Async、@EnableAsync 開啓異步的定時任務調度。

SpringBoot自動配置類對異步的支持:org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration

@Async註解添加

@Async
    @Scheduled(cron = "0/1 * * * * *")
    public void asyncTask() {
        sleep();
        System.out.println(Thread.currentThread().getName() + " async-task 執行,當前時間: " + LocalDateTime.now());
    }

@EnableAsync註解添加

@Configuration
@EnableScheduling  // 同步
@EnableAsync // 異步
public class ScheduleConfig {

}

配置文件

spring:
  task:
    # Spring 執行器配置,對應 TaskExecutionProperties 配置類。對於 Spring 異步任務,會使用該執行器。
    execution:
      thread-name-prefix: async- # 線程池的線程名的前綴。默認爲 task- ,建議根據本身應用來設置
      pool: # 線程池相關
        core-size: 8 # 核心線程數,線程池建立時候初始化的線程數。默認爲 8 。
        max-size: 20 # 最大線程數,線程池最大的線程數,只有在緩衝隊列滿了以後,纔會申請超過核心線程數的線程。默認爲 Integer.MAX_VALUE
        keep-alive: 60s # 容許線程的空閒時間,當超過了核心線程以外的線程,在空閒時間到達以後會被銷燬。默認爲 60 秒
        queue-capacity: 200 # 緩衝隊列大小,用來緩衝執行任務的隊列的大小。默認爲 Integer.MAX_VALUE 。
        allow-core-thread-timeout: true # 是否容許核心線程超時,即開啓線程池的動態增加和縮小。默認爲 true 。
      shutdown:
        await-termination: true # 應用關閉時,是否等待定時任務執行完成。默認爲 false ,建議設置爲 true
        await-termination-period: 60 # 等待任務完成的最大時長,單位爲秒。默認爲 0 ,根據本身應用來設置

同步與異步對比

@Component
public class DemoAsyncTask {

    @Scheduled(cron = "0/1 * * * * *")
    public void synTask() {
        sleep();
        System.out.println(Thread.currentThread().getName() + " syn-task 執行,當前時間: " + LocalDateTime.now());
    }

    @Async
    @Scheduled(cron = "0/1 * * * * *")
    public void asyncTask() {
        sleep();
        System.out.println(Thread.currentThread().getName() + " async-task 執行,當前時間: " + LocalDateTime.now());
    }

    private void sleep() {
        try {
            Thread.sleep(10 * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

同時開啓同步和異步任務,假設任務自己耗時較長,且間隔較短:間隔1s,執行10s,同步與異步執行的差別就此體現。

能夠看到,同步任務並無每間隔1s就執行,而是串行在一塊兒,等前一個任務執行完才執行。而異步任務則不同,成功將串行化的任務並行化。

5、源碼下載

本文內容均爲對優秀博客及官方文檔總結而得,原文地址均已在文中參考閱讀處標註。最後,文中的代碼樣例已經所有上傳至Gitee:https://gitee.com/tqbx/springboot-samples-learn,另有其餘SpringBoot的整合哦。

參考閱讀

相關文章
相關標籤/搜索