解讀@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:延遲啟動,啟動之后指定時(shí)間再執(zhí)行調(diào)度任務(wù)
- @EnableScheduling:開啟任務(wù)調(diào)度,寫在類上只開啟當(dāng)前類中的任務(wù)調(diào)度,如果寫在啟動類上則開啟項(xiàng)目中的所有任務(wù)調(diào)度。
@Slf4j
//加載類型開啟類中,加載啟動類上,開啟整個(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ù)
說明1:fixedRate:按照固定頻率執(zhí)行任務(wù),如每三秒執(zhí)行一次,上一個(gè)任務(wù)下次任務(wù)的開始,由于此時(shí)是單線程,下一個(gè)任務(wù)開始需要等上一個(gè)任務(wù)結(jié)束。
說明2:我們通過Thread.sleep(5000)設(shè)置任務(wù)執(zhí)行需要2s時(shí)間
@Slf4j
//加載類型開啟類中,加載啟動類上,開啟整個(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ù)
說明1:fixedRate:按照固定頻率執(zhí)行任務(wù),如每三秒執(zhí)行一次,上一個(gè)任務(wù)下次任務(wù)的開始,由于此時(shí)是單線程,下一個(gè)任務(wù)開始需要等上一個(gè)任務(wù)結(jié)束。
說明2:我們通過Thread.sleep(5000)設(shè)置任務(wù)執(zhí)行需要5s時(shí)間
@Slf4j
//加載類型開啟類中,加載啟動類上,開啟整個(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、通過公式設(shè)置定時(shí)任務(wù)
cron:可以通過特性的公式設(shè)定定時(shí)任務(wù),任務(wù)生成網(wǎng)站https://cron.qqe2.com/
如:可以設(shè)置每周三下午五點(diǎn)執(zhí)行,每月的月尾執(zhí)行一次等。

如上圖生成的語法表示:每分鐘的前五秒執(zhí)行process
@Slf4j
//加載類型開啟類中,加載啟動類上,開啟整個(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í)候,不能開辟過多的線程,因?yàn)榫€程資源非常的消耗cpu資源,必要的時(shí)候需要使用分布式任務(wù)調(diào)度。
3.1、非多線程的情況
理論上當(dāng)process1結(jié)束的時(shí)候,下次process1啟動的時(shí)候需要等待process2執(zhí)行結(jié)束,否則1不能啟動,應(yīng)該這個(gè)時(shí)候依舊是單線程。
@Slf4j
//加載類型開啟類中,加載啟動類上,開啟整個(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、多線程的情況
在啟動類中定義線程池。值不需要設(shè)置太大,現(xiàn)成對cpu資源消耗大,搞不好容易讓系統(tǒng)宕機(jī)。
設(shè)置多線程后直接啟動程序,繼續(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ù)量
//多線程對cpu資源消耗較大,值不能太大。
taskScheduler.setPoolSize(5);
return taskScheduler;
}
}結(jié)果分析:
process1和process2使用的是不同的線程,一個(gè)線程為taskSheduler-1,一個(gè)線程為taskSheduler-2。
而且process1和process2是同時(shí)啟動的,沒有出現(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的啟動依舊是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、需要再啟動類上開啟異步任務(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ù)量
//多線程對cpu資源消耗較大,值不能太大。
taskScheduler.setPoolSize(10);
return taskScheduler;
}
}4.3、創(chuàng)建process3和process4方法
process3和process3與之前的process1和process2方法一樣都是基于多線程操作。
@Slf4j
//加載類型開啟類中,加載啟動類上,開啟整個(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)文章
解決weblogic部署springboot項(xiàng)目步驟及可能會出現(xiàn)的問題
這篇文章主要介紹了解決weblogic部署springboot項(xiàng)目步驟及可能會出現(xiàn)的問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07
解決springboot項(xiàng)目找不到resources目錄下的資源問題
這篇文章主要介紹了解決springboot項(xiàng)目找不到resources目錄下的資源問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08
java實(shí)現(xiàn)的順時(shí)針/逆時(shí)針打印矩陣操作示例
這篇文章主要介紹了java實(shí)現(xiàn)的順時(shí)針/逆時(shí)針打印矩陣操作,涉及java基于數(shù)組的矩陣存儲、遍歷、打印輸出等相關(guān)操作技巧,需要的朋友可以參考下2019-12-12
Spring Security中集成JWT的完整實(shí)戰(zhàn)指南
本文深入探討了現(xiàn)代Web應(yīng)用中的認(rèn)證授權(quán)方案,對比分析了JWT、Session-Cookie、OAuth2/OIDC、SAML 2.0等主流方案的優(yōu)缺點(diǎn)及適用場景,感興趣的小伙伴可以了解下2026-05-05
Java中常用輸出方式(print() println() printf())
這篇文章主要介紹了Java中常用輸出方式(print() println() printf()),具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-09-09
springboot線程池監(jiān)控的簡單實(shí)現(xiàn)
本文主要介紹了springboot線程池監(jiān)控的簡單實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-01-01
SpringBoot使用@Cacheable注解實(shí)現(xiàn)緩存功能流程詳解
最近一直再學(xué)Spring Boot,在學(xué)習(xí)的過程中也有過很多疑問。為了解答自己的疑惑,也在網(wǎng)上查了一些資料,以下是對@Cacheable注解的一些理解2023-01-01
Java 輕松實(shí)現(xiàn)二維數(shù)組與稀疏數(shù)組互轉(zhuǎn)
在某些應(yīng)用場景中需要大量的二維數(shù)組來進(jìn)行數(shù)據(jù)存儲,但是二維數(shù)組中卻有著大量的無用的位置占據(jù)著內(nèi)存空間,稀疏數(shù)組就是為了優(yōu)化二維數(shù)組,節(jié)省內(nèi)存空間2022-04-04

