解读@Scheduled任务调度/定时任务非分布式

 更新时间:2024年08月05日 08:49:49   作者:雾林小妖  
这篇文章主要介绍了解读@Scheduled任务调度/定时任务非分布式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教

1、功能概述

任务调度就是在规定的时间内执行的任务或者按照固定的频率执行的任务。是非常常见的功能之一。

常见的有JDK原生的Timer, ScheduledThreadPoolExecutor以及springboot提供的@Schduled。分布式调度框架如QuartZ、Elasticjob、XXL-JOB、SchedulerX、PowerJob等。

本文主要讲解非分布式环境下的@Scheduled任务调度讲解,以及@Scheduled结合多线程和@Async异步任务的使用。

当然在任务不是很多的情况下@Scheduled也可以结合如Redis的锁机制实现分布式的任务调度,但是还是建议在分布式环境下,使用分布式调度框架如:QuartZ、Elasticjob、XXL-JOB、SchedulerX、PowerJob等。

2、@Scheduled基本使用

2.1、创建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、按照固定间隔执行

  • fixedDelay:按照固定间隔执行,上一个任务的结束到下一个任务的开始间隔。
  • initialDealay:延迟启动,启动之后指定时间再执行调度任务
  • @EnableScheduling:开启任务调度,写在类上只开启当前类中的任务调度,如果写在启动类上则开启项目中的所有任务调度。
@Slf4j
//加载类型开启类中,加载启动类上,开启整个项目
@EnableScheduling //是否开启
@Component
public class MyScheduled {
    @Scheduled(fixedDelay = 3000,initialDelay = 3000)
    public void process(){
        log.info("=====process执行========"+ LocalDateTime.now());
    }
}

结果分析:

从输出结果中可以看出,程序每隔3s执行一次

2.3、按照固定频率执行任务

说明1fixedRate:按照固定频率执行任务,如每三秒执行一次,上一个任务下次任务的开始,由于此时是单线程,下一个任务开始需要等上一个任务结束。

说明2:我们通过Thread.sleep(5000)设置任务执行需要2s时间

@Slf4j
//加载类型开启类中,加载启动类上,开启整个项目
@EnableScheduling //是否开启
@Component
public class MyScheduled {
@Scheduled(fixedRate = 3000,initialDelay = 3000)
public void process() throws InterruptedException {
log.info("=====process执行fixedRate开始========"+ LocalDateTime.now());
      Thread.sleep(2000);
      log.info("=====process执行fixedRate结束========"+ LocalDateTime.now());
    }
}

结果分析:

从结果中可以看出由于设置process执行的时间为2s钟,process按照固定的频率(3s)每3s执行一次,第一次开始是22:19:22,第二次开始是22:19:25

2.4、按照固定频率执行任务

说明1fixedRate:按照固定频率执行任务,如每三秒执行一次,上一个任务下次任务的开始,由于此时是单线程,下一个任务开始需要等上一个任务结束。

说明2:我们通过Thread.sleep(5000)设置任务执行需要5s时间

@Slf4j
//加载类型开启类中,加载启动类上,开启整个项目
@EnableScheduling //是否开启
@Component
public class MyScheduled {
@Scheduled(fixedRate = 3000,initialDelay = 3000)
public void process() throws InterruptedException {
  log.info("=====process执行fixedRate开始========"+ LocalDateTime.now());
      Thread.sleep(5000);
      log.info("=====process执行fixedRate结束========"+ LocalDateTime.now());
    }
}

结果分析:

从结果可以看出:虽然设置固定的频率是3s,但是由于在单线程情况下下次任务的开启需要等待上一个任务的结束,第一次任务开始时间为22:17:43,第二次任务开启时间为22:17:48中间间隔了5s钟。

2.5、通过公式设置定时任务

cron:可以通过特性的公式设定定时任务,任务生成网站https://cron.qqe2.com/

如:可以设置每周三下午五点执行,每月的月尾执行一次等。

如上图生成的语法表示:每分钟的前五秒执行process

@Slf4j
//加载类型开启类中,加载启动类上,开启整个项目
@EnableScheduling //是否开启
@Component
public class MyScheduled {
@Scheduled(cron ="0,1,2,3,4 * * * * ? ")
public void process() throws InterruptedException {
     log.info("=====process执行fixedRate开始========"+ LocalDateTime.now());
     Thread.sleep(5000);
     log.info("=====process执行fixedRate结束========"+ LocalDateTime.now());
 }
}

结果分析:

从图中可以看出每每分钟开始的时候执行,五秒后结束。

3、@Scheduled与多线程

加入多线程的目的是为了程序执行的效率能够提高。但是在设置多线程的时候,不能开辟过多的线程,因为线程资源非常的消耗cpu资源,必要的时候需要使用分布式任务调度。

3.1、非多线程的情况

理论上当process1结束的时候,下次process1启动的时候需要等待process2执行结束,否则1不能启动,应该这个时候依旧是单线程。

@Slf4j
//加载类型开启类中,加载启动类上,开启整个项目
@EnableScheduling //是否开启
@Component
public class MyScheduled {
@Scheduled(fixedDelay = 3000)
public void process1() throws InterruptedException {
log.info("=====process1执行开始========"+ LocalDateTime.now());
        Thread.sleep(5000);
        log.info("=====process1执行结束========"+ LocalDateTime.now());
    }
@Scheduled(fixedDelay = 3000)
public void process2() throws InterruptedException {
       log.info("=====process2执行开始========"+ LocalDateTime.now());
       Thread.sleep(5000);
       log.info("=====process2执行结束========"+ LocalDateTime.now());
    }
}

结果分析:

从输出结果可以看出process2的开始是等到process1结束后才执行的。

3.2、多线程的情况

在启动类中定义线程池。值不需要设置太大,现成对cpu资源消耗大,搞不好容易让系统宕机。

设置多线程后直接启动程序,继续观看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();
        //设置线程池中线程的数量
        //多线程对cpu资源消耗较大,值不能太大。
        taskScheduler.setPoolSize(5);
        return taskScheduler;
    }
 
}

结果分析:

process1和process2使用的是不同的线程,一个线程为taskSheduler-1,一个线程为taskSheduler-2。

而且process1和process2是同时启动的,没有出现相互等待的情况,因为现在使用的是多线程的情况。

4、@Scheduled与@ Async异步任务

在上面的案例中虽然process1和process2同时执行了,没有出现相互等待的情况。但是第二次process1和process2执行依旧是等待程序5s结束后再等待3是执行。

name如何能够实现即使process1执行时间为5s,但是下一次process1的启动依旧是3s后。而不是当前的8是后。这就可以使用异步任务@Async。当然复杂的异步任务还是建议使用如MQ技术。

注意点:@Async的使用需要写在单独的一个类中,不能与当前调用业务写在一起,否则不生效。

4.1、创建异步任务类及异步方法

@Component
public class AsyncTaskScheduled {
    @Async//那个方法需要使用异步调用,就使用该注解
    public void asyncMethod() {
        try{
            Thread.sleep(6000);//模拟异步执行业务的时间
        }catch (Exception e){
            System.out.println(e.getStackTrace());
        }
    }
}

4.2、需要再启动类上开启异步任务

@EnableAsync:开启异步任务调度

@SpringBootApplication
@EnableAsync
public class ScheduleddemoApplication {
 
    public static void main(String[] args) {
        SpringApplication.run(ScheduleddemoApplication.class, args);
    }
 
    @Bean
    public TaskScheduler taskScheduler(){
        ThreadPoolTaskScheduler taskScheduler=new ThreadPoolTaskScheduler();
        //设置线程池中线程的数量
        //多线程对cpu资源消耗较大,值不能太大。
        taskScheduler.setPoolSize(10);
        return taskScheduler;
    }
 
}

4.3、创建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执行开始========"+ LocalDateTime.now());
        asyncTaskScheduled.asyncMethod();
        log.info("=====process3执行结束========"+ LocalDateTime.now());
    }
@Scheduled(fixedDelay = 3000)
public void process4() throws InterruptedException {
        log.info("=====process4执行开始========"+ LocalDateTime.now());
        asyncTaskScheduled.asyncMethod();
        log.info("=====process4执行结束========"+ LocalDateTime.now());
    }
}

结果分析:

从结果可以看出,虽然异步任务执行的时间为6s,但是process4第一次开始和第二次开始的时间间隔为3s.

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • mybatis-plus 插入修改配置默认值的实现方式

    mybatis-plus 插入修改配置默认值的实现方式

    这篇文章主要介绍了mybatis-plus 插入修改配置默认值的实现方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-07-07
  • Java overload和override的区别分析

    Java overload和override的区别分析

    方法的重写(Overriding)和重载(Overloading)是Java多态性的不同表现,想要了解更多请参考本文
    2012-11-11
  • PowerJob的OmsLogHandler工作流程源码解析

    PowerJob的OmsLogHandler工作流程源码解析

    这篇文章主要为大家介绍了PowerJob的OmsLogHandler工作流程源码解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-12-12
  • Spring Boot和Hazelcast使用详解

    Spring Boot和Hazelcast使用详解

    这篇文章主要介绍了Spring Boot和Hazelcast使用详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-09-09
  • 利用SpringBoot实现多数据源的两种方式总结

    利用SpringBoot实现多数据源的两种方式总结

    关于动态数据源的切换的方案有很多,核心只有两种,一种是构建多套环境,另一种是基于spring原生的AbstractRoutingDataSource切换,这篇文章主要给大家介绍了关于利用SpringBoot实现多数据源的两种方式,需要的朋友可以参考下
    2021-10-10
  • Struts 2 实现Action的几种方式

    Struts 2 实现Action的几种方式

    本篇文章主要介绍了Struts 2 实现Action的几种方式,Struts 2框架下实现Action类有三种方式,有兴趣的可以了解一下
    2017-10-10
  • Netty4之如何实现HTTP请求、响应

    Netty4之如何实现HTTP请求、响应

    这篇文章主要介绍了Netty4之如何实现HTTP请求、响应问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-04-04
  • 解决SpringBoot log4j日志没生成的问题

    解决SpringBoot log4j日志没生成的问题

    这篇文章主要介绍了解决SpringBoot log4j日志没生成的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-07-07
  • 内存屏障由来及实现思路

    内存屏障由来及实现思路

    这篇文章主要为大家详细介绍了内存屏障由来及实现思路的详细讲解,让大家彻底的理解内存屏障,有需要的朋友可以借鉴参考下,希望能够有所帮助
    2022-01-01
  • java实现超市商品库存管理平台

    java实现超市商品库存管理平台

    这篇文章主要为大家详细介绍了java实现超市商品库存管理平台,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-10-10

最新评论