解讀@Scheduled任務調(diào)度/定時任務非分布式
1、功能概述
任務調(diào)度就是在規(guī)定的時間內(nèi)執(zhí)行的任務或者按照固定的頻率執(zhí)行的任務。是非常常見的功能之一。
常見的有JDK原生的Timer, ScheduledThreadPoolExecutor以及springboot提供的@Schduled。分布式調(diào)度框架如QuartZ、Elasticjob、XXL-JOB、SchedulerX、PowerJob等。
本文主要講解非分布式環(huán)境下的@Scheduled任務調(diào)度講解,以及@Scheduled結(jié)合多線程和@Async異步任務的使用。
當然在任務不是很多的情況下@Scheduled也可以結(jié)合如Redis的鎖機制實現(xiàn)分布式的任務調(diào)度,但是還是建議在分布式環(huán)境下,使用分布式調(diào)度框架如:QuartZ、Elasticjob、XXL-JOB、SchedulerX、PowerJob等。
2、@Scheduled基本使用
2.1、創(chuàng)建springboot工程引入包信息
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.1.6</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.txc</groupId> <artifactId>scheduleddemo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>scheduleddemo</name> <description>scheduleddemo</description> <properties> <java.version>17</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <image> <builder>paketobuildpacks/builder-jammy-base:latest</builder> </image> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project>
2.2、按照固定間隔執(zhí)行
- fixedDelay:按照固定間隔執(zhí)行,上一個任務的結(jié)束到下一個任務的開始間隔。
- initialDealay:延遲啟動,啟動之后指定時間再執(zhí)行調(diào)度任務
- @EnableScheduling:開啟任務調(diào)度,寫在類上只開啟當前類中的任務調(diào)度,如果寫在啟動類上則開啟項目中的所有任務調(diào)度。
@Slf4j //加載類型開啟類中,加載啟動類上,開啟整個項目 @EnableScheduling //是否開啟 @Component public class MyScheduled { @Scheduled(fixedDelay = 3000,initialDelay = 3000) public void process(){ log.info("=====process執(zhí)行========"+ LocalDateTime.now()); } }
結(jié)果分析:
從輸出結(jié)果中可以看出,程序每隔3s執(zhí)行一次
2.3、按照固定頻率執(zhí)行任務
說明1:fixedRate:按照固定頻率執(zhí)行任務,如每三秒執(zhí)行一次,上一個任務下次任務的開始,由于此時是單線程,下一個任務開始需要等上一個任務結(jié)束。
說明2:我們通過Thread.sleep(5000)設置任務執(zhí)行需要2s時間
@Slf4j //加載類型開啟類中,加載啟動類上,開啟整個項目 @EnableScheduling //是否開啟 @Component public class MyScheduled { @Scheduled(fixedRate = 3000,initialDelay = 3000) public void process() throws InterruptedException { log.info("=====process執(zhí)行fixedRate開始========"+ LocalDateTime.now()); Thread.sleep(2000); log.info("=====process執(zhí)行fixedRate結(jié)束========"+ LocalDateTime.now()); } }
結(jié)果分析:
從結(jié)果中可以看出由于設置process執(zhí)行的時間為2s鐘,process按照固定的頻率(3s)每3s執(zhí)行一次,第一次開始是22:19:22,第二次開始是22:19:25
2.4、按照固定頻率執(zhí)行任務
說明1:fixedRate:按照固定頻率執(zhí)行任務,如每三秒執(zhí)行一次,上一個任務下次任務的開始,由于此時是單線程,下一個任務開始需要等上一個任務結(jié)束。
說明2:我們通過Thread.sleep(5000)設置任務執(zhí)行需要5s時間
@Slf4j //加載類型開啟類中,加載啟動類上,開啟整個項目 @EnableScheduling //是否開啟 @Component public class MyScheduled { @Scheduled(fixedRate = 3000,initialDelay = 3000) public void process() throws InterruptedException { log.info("=====process執(zhí)行fixedRate開始========"+ LocalDateTime.now()); Thread.sleep(5000); log.info("=====process執(zhí)行fixedRate結(jié)束========"+ LocalDateTime.now()); } }
結(jié)果分析:
從結(jié)果可以看出:雖然設置固定的頻率是3s,但是由于在單線程情況下下次任務的開啟需要等待上一個任務的結(jié)束,第一次任務開始時間為22:17:43,第二次任務開啟時間為22:17:48中間間隔了5s鐘。
2.5、通過公式設置定時任務
cron:可以通過特性的公式設定定時任務,任務生成網(wǎng)站https://cron.qqe2.com/
如:可以設置每周三下午五點執(zhí)行,每月的月尾執(zhí)行一次等。
如上圖生成的語法表示:每分鐘的前五秒執(zhí)行process
@Slf4j //加載類型開啟類中,加載啟動類上,開啟整個項目 @EnableScheduling //是否開啟 @Component public class MyScheduled { @Scheduled(cron ="0,1,2,3,4 * * * * ? ") public void process() throws InterruptedException { log.info("=====process執(zhí)行fixedRate開始========"+ LocalDateTime.now()); Thread.sleep(5000); log.info("=====process執(zhí)行fixedRate結(jié)束========"+ LocalDateTime.now()); } }
結(jié)果分析:
從圖中可以看出每每分鐘開始的時候執(zhí)行,五秒后結(jié)束。
3、@Scheduled與多線程
加入多線程的目的是為了程序執(zhí)行的效率能夠提高。但是在設置多線程的時候,不能開辟過多的線程,因為線程資源非常的消耗cpu資源,必要的時候需要使用分布式任務調(diào)度。
3.1、非多線程的情況
理論上當process1結(jié)束的時候,下次process1啟動的時候需要等待process2執(zhí)行結(jié)束,否則1不能啟動,應該這個時候依舊是單線程。
@Slf4j //加載類型開啟類中,加載啟動類上,開啟整個項目 @EnableScheduling //是否開啟 @Component public class MyScheduled { @Scheduled(fixedDelay = 3000) public void process1() throws InterruptedException { log.info("=====process1執(zhí)行開始========"+ LocalDateTime.now()); Thread.sleep(5000); log.info("=====process1執(zhí)行結(jié)束========"+ LocalDateTime.now()); } @Scheduled(fixedDelay = 3000) public void process2() throws InterruptedException { log.info("=====process2執(zhí)行開始========"+ LocalDateTime.now()); Thread.sleep(5000); log.info("=====process2執(zhí)行結(jié)束========"+ LocalDateTime.now()); } }
結(jié)果分析:
從輸出結(jié)果可以看出process2的開始是等到process1結(jié)束后才執(zhí)行的。
3.2、多線程的情況
在啟動類中定義線程池。值不需要設置太大,現(xiàn)成對cpu資源消耗大,搞不好容易讓系統(tǒng)宕機。
設置多線程后直接啟動程序,繼續(xù)觀看process1和process2的輸出情況。
package com.txc.scheduleddemo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; @SpringBootApplication public class ScheduleddemoApplication { public static void main(String[] args) { SpringApplication.run(ScheduleddemoApplication.class, args); } @Bean public TaskScheduler taskScheduler(){ ThreadPoolTaskScheduler taskScheduler=new ThreadPoolTaskScheduler(); //設置線程池中線程的數(shù)量 //多線程對cpu資源消耗較大,值不能太大。 taskScheduler.setPoolSize(5); return taskScheduler; } }
結(jié)果分析:
process1和process2使用的是不同的線程,一個線程為taskSheduler-1,一個線程為taskSheduler-2。
而且process1和process2是同時啟動的,沒有出現(xiàn)相互等待的情況,因為現(xiàn)在使用的是多線程的情況。
4、@Scheduled與@ Async異步任務
在上面的案例中雖然process1和process2同時執(zhí)行了,沒有出現(xiàn)相互等待的情況。但是第二次process1和process2執(zhí)行依舊是等待程序5s結(jié)束后再等待3是執(zhí)行。
name如何能夠?qū)崿F(xiàn)即使process1執(zhí)行時間為5s,但是下一次process1的啟動依舊是3s后。而不是當前的8是后。這就可以使用異步任務@Async。當然復雜的異步任務還是建議使用如MQ技術。
注意點:@Async的使用需要寫在單獨的一個類中,不能與當前調(diào)用業(yè)務寫在一起,否則不生效。
4.1、創(chuàng)建異步任務類及異步方法
@Component public class AsyncTaskScheduled { @Async//那個方法需要使用異步調(diào)用,就使用該注解 public void asyncMethod() { try{ Thread.sleep(6000);//模擬異步執(zhí)行業(yè)務的時間 }catch (Exception e){ System.out.println(e.getStackTrace()); } } }
4.2、需要再啟動類上開啟異步任務
@EnableAsync:開啟異步任務調(diào)度
@SpringBootApplication @EnableAsync public class ScheduleddemoApplication { public static void main(String[] args) { SpringApplication.run(ScheduleddemoApplication.class, args); } @Bean public TaskScheduler taskScheduler(){ ThreadPoolTaskScheduler taskScheduler=new ThreadPoolTaskScheduler(); //設置線程池中線程的數(shù)量 //多線程對cpu資源消耗較大,值不能太大。 taskScheduler.setPoolSize(10); return taskScheduler; } }
4.3、創(chuàng)建process3和process4方法
process3和process3與之前的process1和process2方法一樣都是基于多線程操作。
@Slf4j //加載類型開啟類中,加載啟動類上,開啟整個項目 @EnableScheduling //是否開啟 @Component public class MyScheduled { @Autowired AsyncTaskScheduled asyncTaskScheduled; @Scheduled(fixedDelay = 3000) public void process3() throws InterruptedException { log.info("=====process3執(zhí)行開始========"+ LocalDateTime.now()); asyncTaskScheduled.asyncMethod(); log.info("=====process3執(zhí)行結(jié)束========"+ LocalDateTime.now()); } @Scheduled(fixedDelay = 3000) public void process4() throws InterruptedException { log.info("=====process4執(zhí)行開始========"+ LocalDateTime.now()); asyncTaskScheduled.asyncMethod(); log.info("=====process4執(zhí)行結(jié)束========"+ LocalDateTime.now()); } }
結(jié)果分析:
從結(jié)果可以看出,雖然異步任務執(zhí)行的時間為6s,但是process4第一次開始和第二次開始的時間間隔為3s.
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
mybatis-plus?插入修改配置默認值的實現(xiàn)方式
這篇文章主要介紹了mybatis-plus?插入修改配置默認值的實現(xiàn)方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-07-07Java overload和override的區(qū)別分析
方法的重寫(Overriding)和重載(Overloading)是Java多態(tài)性的不同表現(xiàn),想要了解更多請參考本文2012-11-11PowerJob的OmsLogHandler工作流程源碼解析
這篇文章主要為大家介紹了PowerJob的OmsLogHandler工作流程源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-12-12利用SpringBoot實現(xiàn)多數(shù)據(jù)源的兩種方式總結(jié)
關于動態(tài)數(shù)據(jù)源的切換的方案有很多,核心只有兩種,一種是構(gòu)建多套環(huán)境,另一種是基于spring原生的AbstractRoutingDataSource切換,這篇文章主要給大家介紹了關于利用SpringBoot實現(xiàn)多數(shù)據(jù)源的兩種方式,需要的朋友可以參考下2021-10-10