解讀@Scheduled任務(wù)調(diào)度/定時(shí)任務(wù)非分布式
1、功能概述
任務(wù)調(diào)度就是在規(guī)定的時(shí)間內(nèi)執(zhí)行的任務(wù)或者按照固定的頻率執(zhí)行的任務(wù)。是非常常見的功能之一。
常見的有JDK原生的Timer, ScheduledThreadPoolExecutor以及springboot提供的@Schduled。分布式調(diào)度框架如QuartZ、Elasticjob、XXL-JOB、SchedulerX、PowerJob等。
本文主要講解非分布式環(huán)境下的@Scheduled任務(wù)調(diào)度講解,以及@Scheduled結(jié)合多線程和@Async異步任務(wù)的使用。
當(dāng)然在任務(wù)不是很多的情況下@Scheduled也可以結(jié)合如Redis的鎖機(jī)制實(shí)現(xiàn)分布式的任務(wù)調(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í)行,上一個(gè)任務(wù)的結(jié)束到下一個(gè)任務(wù)的開始間隔。
- initialDealay:延遲啟動(dòng),啟動(dòng)之后指定時(shí)間再執(zhí)行調(diào)度任務(wù)
- @EnableScheduling:開啟任務(wù)調(diào)度,寫在類上只開啟當(dāng)前類中的任務(wù)調(diào)度,如果寫在啟動(dòng)類上則開啟項(xiàng)目中的所有任務(wù)調(diào)度。
@Slf4j //加載類型開啟類中,加載啟動(dòng)類上,開啟整個(gè)項(xiàng)目 @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í)行任務(wù)
說(shuō)明1:fixedRate:按照固定頻率執(zhí)行任務(wù),如每三秒執(zhí)行一次,上一個(gè)任務(wù)下次任務(wù)的開始,由于此時(shí)是單線程,下一個(gè)任務(wù)開始需要等上一個(gè)任務(wù)結(jié)束。
說(shuō)明2:我們通過(guò)Thread.sleep(5000)設(shè)置任務(wù)執(zhí)行需要2s時(shí)間
@Slf4j //加載類型開啟類中,加載啟動(dòng)類上,開啟整個(gè)項(xiàng)目 @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é)果中可以看出由于設(shè)置process執(zhí)行的時(shí)間為2s鐘,process按照固定的頻率(3s)每3s執(zhí)行一次,第一次開始是22:19:22,第二次開始是22:19:25
2.4、按照固定頻率執(zhí)行任務(wù)
說(shuō)明1:fixedRate:按照固定頻率執(zhí)行任務(wù),如每三秒執(zhí)行一次,上一個(gè)任務(wù)下次任務(wù)的開始,由于此時(shí)是單線程,下一個(gè)任務(wù)開始需要等上一個(gè)任務(wù)結(jié)束。
說(shuō)明2:我們通過(guò)Thread.sleep(5000)設(shè)置任務(wù)執(zhí)行需要5s時(shí)間
@Slf4j //加載類型開啟類中,加載啟動(dòng)類上,開啟整個(gè)項(xiàng)目 @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é)果可以看出:雖然設(shè)置固定的頻率是3s,但是由于在單線程情況下下次任務(wù)的開啟需要等待上一個(gè)任務(wù)的結(jié)束,第一次任務(wù)開始時(shí)間為22:17:43,第二次任務(wù)開啟時(shí)間為22:17:48中間間隔了5s鐘。
2.5、通過(guò)公式設(shè)置定時(shí)任務(wù)
cron:可以通過(guò)特性的公式設(shè)定定時(shí)任務(wù),任務(wù)生成網(wǎng)站https://cron.qqe2.com/
如:可以設(shè)置每周三下午五點(diǎn)執(zhí)行,每月的月尾執(zhí)行一次等。
如上圖生成的語(yǔ)法表示:每分鐘的前五秒執(zhí)行process
@Slf4j //加載類型開啟類中,加載啟動(dòng)類上,開啟整個(gè)項(xiàng)目 @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é)果分析:
從圖中可以看出每每分鐘開始的時(shí)候執(zhí)行,五秒后結(jié)束。
3、@Scheduled與多線程
加入多線程的目的是為了程序執(zhí)行的效率能夠提高。但是在設(shè)置多線程的時(shí)候,不能開辟過(guò)多的線程,因?yàn)榫€程資源非常的消耗cpu資源,必要的時(shí)候需要使用分布式任務(wù)調(diào)度。
3.1、非多線程的情況
理論上當(dāng)process1結(jié)束的時(shí)候,下次process1啟動(dòng)的時(shí)候需要等待process2執(zhí)行結(jié)束,否則1不能啟動(dòng),應(yīng)該這個(gè)時(shí)候依舊是單線程。
@Slf4j //加載類型開啟類中,加載啟動(dòng)類上,開啟整個(gè)項(xiàng)目 @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、多線程的情況
在啟動(dòng)類中定義線程池。值不需要設(shè)置太大,現(xiàn)成對(duì)cpu資源消耗大,搞不好容易讓系統(tǒng)宕機(jī)。
設(shè)置多線程后直接啟動(dò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è)置線程池中線程的數(shù)量 //多線程對(duì)cpu資源消耗較大,值不能太大。 taskScheduler.setPoolSize(5); return taskScheduler; } }
結(jié)果分析:
process1和process2使用的是不同的線程,一個(gè)線程為taskSheduler-1,一個(gè)線程為taskSheduler-2。
而且process1和process2是同時(shí)啟動(dòng)的,沒有出現(xiàn)相互等待的情況,因?yàn)楝F(xiàn)在使用的是多線程的情況。
4、@Scheduled與@ Async異步任務(wù)
在上面的案例中雖然process1和process2同時(shí)執(zhí)行了,沒有出現(xiàn)相互等待的情況。但是第二次process1和process2執(zhí)行依舊是等待程序5s結(jié)束后再等待3是執(zhí)行。
name如何能夠?qū)崿F(xiàn)即使process1執(zhí)行時(shí)間為5s,但是下一次process1的啟動(dòng)依舊是3s后。而不是當(dāng)前的8是后。這就可以使用異步任務(wù)@Async。當(dāng)然復(fù)雜的異步任務(wù)還是建議使用如MQ技術(shù)。
注意點(diǎn):@Async的使用需要寫在單獨(dú)的一個(gè)類中,不能與當(dāng)前調(diào)用業(yè)務(wù)寫在一起,否則不生效。
4.1、創(chuàng)建異步任務(wù)類及異步方法
@Component public class AsyncTaskScheduled { @Async//那個(gè)方法需要使用異步調(diào)用,就使用該注解 public void asyncMethod() { try{ Thread.sleep(6000);//模擬異步執(zhí)行業(yè)務(wù)的時(shí)間 }catch (Exception e){ System.out.println(e.getStackTrace()); } } }
4.2、需要再啟動(dòng)類上開啟異步任務(wù)
@EnableAsync:開啟異步任務(wù)調(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è)置線程池中線程的數(shù)量 //多線程對(duì)cpu資源消耗較大,值不能太大。 taskScheduler.setPoolSize(10); return taskScheduler; } }
4.3、創(chuàng)建process3和process4方法
process3和process3與之前的process1和process2方法一樣都是基于多線程操作。
@Slf4j //加載類型開啟類中,加載啟動(dòng)類上,開啟整個(gè)項(xiàng)目 @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é)果可以看出,雖然異步任務(wù)執(zhí)行的時(shí)間為6s,但是process4第一次開始和第二次開始的時(shí)間間隔為3s.
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
mybatis-plus?插入修改配置默認(rèn)值的實(shí)現(xiàn)方式
這篇文章主要介紹了mybatis-plus?插入修改配置默認(rèn)值的實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-07-07Java overload和override的區(qū)別分析
方法的重寫(Overriding)和重載(Overloading)是Java多態(tài)性的不同表現(xiàn),想要了解更多請(qǐng)參考本文2012-11-11PowerJob的OmsLogHandler工作流程源碼解析
這篇文章主要為大家介紹了PowerJob的OmsLogHandler工作流程源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12利用SpringBoot實(shí)現(xiàn)多數(shù)據(jù)源的兩種方式總結(jié)
關(guān)于動(dòng)態(tài)數(shù)據(jù)源的切換的方案有很多,核心只有兩種,一種是構(gòu)建多套環(huán)境,另一種是基于spring原生的AbstractRoutingDataSource切換,這篇文章主要給大家介紹了關(guān)于利用SpringBoot實(shí)現(xiàn)多數(shù)據(jù)源的兩種方式,需要的朋友可以參考下2021-10-10Struts 2 實(shí)現(xiàn)Action的幾種方式
本篇文章主要介紹了Struts 2 實(shí)現(xiàn)Action的幾種方式,Struts 2框架下實(shí)現(xiàn)Action類有三種方式,有興趣的可以了解一下2017-10-10Netty4之如何實(shí)現(xiàn)HTTP請(qǐng)求、響應(yīng)
這篇文章主要介紹了Netty4之如何實(shí)現(xiàn)HTTP請(qǐng)求、響應(yīng)問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-04-04內(nèi)存屏障由來(lái)及實(shí)現(xiàn)思路
這篇文章主要為大家詳細(xì)介紹了內(nèi)存屏障由來(lái)及實(shí)現(xiàn)思路的詳細(xì)講解,讓大家徹底的理解內(nèi)存屏障,有需要的朋友可以借鑒參考下,希望能夠有所幫助2022-01-01java實(shí)現(xiàn)超市商品庫(kù)存管理平臺(tái)
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)超市商品庫(kù)存管理平臺(tái),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-10-10