定时任务的几种实现方式

定时任务实现的几种方式:

1、JDK自带

  • (1)Timer:这是java自带的java.util.Timer类,这个类允许你调度一个java.util.TimerTask任务。使用这种方式可以让你的程序按照某一个频度执行,但不能在指定时间运行。一般用的较少。
  • (2)ScheduledExecutorService:也jdk自带的一个类;是基于线程池设计的定时任务类,每个调度任务都会分配到线程池中的一个线程去执行,也就是说,任务是并发执行,互不影响。

2、Spring Task

  • Spring Task :Spring3.0以后自带的task,Spring Boot提供的用于定时任务控制的注解**@Scheduled注解**,可以将它看成一个轻量级的Quartz,而且使用起来比Quartz简单许多。

3、第三方任务调度框架

除了使用Spring框架提供的 @Scheduled 注解和SchedulingConfigurer接口外,还有许多第三方的任务调度库可供选择。这些库通常提供了更多的功能和灵活性,以满足各种复杂的任务调度需求。以下是一些常见的第三方任务调度库:

  • Quartz Scheduler:是一个功能强大且灵活的任务调度库,具有丰富的功能,如支持基于cron表达式的任务调度、集群支持、作业持久化等。它可以与Spring框架集成,并且被广泛应用于各种类型的任务调度应用程序中。
  • xxl-job:是一个分布式任务调度平台,提供了可视化的任务管理界面和多种任务调度方式,如单机任务、分布式任务、定时任务等。它支持任务执行日志、任务失败重试、动态调整任务执行策略等功能。
  • Elastic Job:是一个分布式任务调度框架,可以轻松实现分布式任务调度和作业执行。它提供了分布式任务执行、作业依赖关系、作业分片等功能,适用于大规模的分布式任务调度场景。
  • PowerJob:是一个开源的分布式任务调度框架,由阿里巴巴集团开发并开源。PowerJob 提供了分布式、高可用的任务调度能力,支持多种任务类型,如定时任务、延时任务、流程任务等。

目录

一、JDK自带

1、使用Timer

2、使用ScheduledExecutorService

[二、Spring Task](#二、Spring Task)

1、基础使用案例

2、参数说明简介

3、cron表达式

(1)cron参数配置描述

(2)cron通配符描述

4、cron案例

5、实现多任务并行(默认单线程)

6、局限性(不支持年份设定)


一、JDK自带

1、使用Timer

java 复制代码
/**
 * Timer:这是java自带的java.util.Timer类
 *
 * 这个类允许你调度一个java.util.TimerTask任务
 * 使用这种方式可以让你的程序按照某一个频度执行,但不能在指定时间运行
 * 【一般用的较少】
 */
@Configuration
public class JDKTimerTask {

	@Bean
	public void test(){
		TimerTask timerTask = new TimerTask() {
			@Override
			public void run() {
				System.err.println("task  run:"+ LocalDateTime.now().toLocalTime() + "\r\n线程 : " + Thread.currentThread().getName());
			}
		};
		Timer timer = new Timer();
		//安排指定的任务在指定的时间开始进行重复的固定延迟执行。这里是每3秒执行一次
		timer.schedule(timerTask,10,3000);
	}
}

执行结果

2、使用ScheduledExecutorService

java 复制代码
/**
 * ScheduledExecutorService:也jdk自带的一个类;
 * 是基于线程池设计的定时任务类,每个调度任务都会分配到线程池中的一个线程去执行
 * 也就是说,任务是并发执行,互不影响。
 */
@Configuration
public class ScheduledExecutorServiceTimerTask {

	@Bean
	public void test(){
		ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
		// 参数:1、任务体 2、首次执行的延时时间
		//      3、任务执行间隔 4、间隔时间单位
		service.scheduleAtFixedRate(()->System.out.println("task ScheduledExecutorService "+ LocalDateTime.now().toLocalTime() + "\r\n线程 : " + Thread.currentThread().getName()), 0, 3, TimeUnit.SECONDS);

	}
}

执行结果:

二、Spring Task

@Scheduled注解是Spring Boot提供的用于定时任务控制的注解,主要用于控制任务在某个指定时间执行,或者每隔一段时间执行

@Scheduled需要配合**@EnableScheduling**使用。使用时,将@Scheduled注解放在待定时的方法名上方,将 @EnableScheduling放在项目主启动类类名上方。@Scheduled主要有三种配置执行时间的方式:cronfixedRatefixedDelay

注意:@Scheduled是不支持年份设置,spring quartz支持

1、基础使用案例

(1)主启动类增加注解**@EnableScheduling**:表明开启定时任务

java 复制代码
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableScheduling //开启定时任务注解
public class TimedTaskDemoApplication {

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

}

(2)定时任务执行方法上增加注解**@Scheduled**:表明该方法为定时任务需要执行的内容

java 复制代码
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.Date;

@Component
@Slf4j
public class TaskDemo {

//    @Scheduled(cron = "0 0 4 ? * SAT")//每周六凌晨4:00
    @Scheduled(cron = "* * * * * *")//每秒执行一次
    private void doTask(){
        log.info("执行定时任务:" + new Date());
    }
}

2、参数说明简介

| 参数 | 说明 | |
| cron | 任务执行的cron表达式 | |
| zone | cron表达时解析使用的时区,默认为服务器的本地时区。 使用java.util.TimeZone#getTimeZone(String)方法解析 | GMT-8:00 |
| fixedRate | 固定速率 上一次任务执行开始到下一次执行开始的间隔时间固定,单位为ms。 若在调度任务执行时,上一次任务还未执行完毕,会加入worker队列,等待上一次执行完成后,马上执行下一次任务 | 1000 |
| fixedRateString | 与fixedRate一致,只是间隔时间使用java.time.Duration#parse解析 | 1000或PT1S |
| fixedDelay | 固定延迟 上一次任务执行结束到下一次执行开始的间隔时间固定,单位为ms。 | 1000 |
| fixedDelayString | 与fixedDelay一致,只是间隔时间使用java.time.Duration#parse解析 | 1000或PT1S |
| initialDelay | 首次延迟多长时间后执行,单位ms。 之后按照fixedRate、fixedRateString、fixedDelay、fixedDelayString指定的规则执行,需要指定其中一个规则。 注意:不能和cron一起使用 | 1000 |

initialDelayString 与initialDelay 一致,只是间隔时间使用java.time.Duration#parse解析 1000或PT1S

关于 fixedRate 和 fixedDelay的区别:

fixedRate 的间隔时间是上次任务开始后,开始计算时间间隔,达到指定时间间隔后,开始执行下一次任务

fixedDelay 的间隔时间是上次任务结束后,开始计算时间间隔,达到指定时间间隔后,开始执行下一次任务

例如:当一个任务需要统计大量数据,并根据不用用户生成不同文件报表,并将生成的报表推送到相应的用户下,该任务有补充机制。在执行该任务时,将使用大量时间时,此时使用 fixedRate 就会造成工作队列长时间堆积,同时,如果使用 fixedRate 的话,与 cron 的作用高度重合,基本可以用 cron 表达式替代,这个情况下,使用 fixedDelay 效果更好,这是在一个任务结束后,再次执行该任务进行重试,直到所有用户都拿到相应的文件。

3、cron表达式

cron@Scheduled的一个参数,是一个字符串,以空格隔开

@Scheduled(cron = "{秒数} {分钟} {小时} {日期} {月份} {星期}")

(1)cron参数配置描述

| 单位 | 允许值 | 允许通配符 |
| 秒 | 0-59 | , - * / |
| 分钟 | 0-59 | , - * / |
| 小时 | 0-23 | , - * / |
| 日期 | 1-31 | , - * / ? L W |
| 月份 | 1-12或JAN-DEC(大小写均可) | , - * / ? |

星期 1-7或SUN-SAT(大小写均可) 注:星期日为每周第一天,所以1-7表示周末到周六 , - * / ? L #

(2)cron通配符描述

| 符号 | 含义 |
| * | 所有值,在秒字段上表示每秒执行,在月字段上表示每月执行 |
| ? | 不指定值,不需要关心当前指定的字段的值,比如每天都执行但不需要关心周几就可以把周的字段设为? |
| - | 区间或者范围,如秒的0-2 ,表示0秒、1秒、2秒都会触发 |
| , | 指定值,比如在0秒、20秒、25秒触发,可以把秒的字段设为0,20,25 |
| / | 递增触发,比如秒的字段上设0/3 ,表示从第0秒开始,每隔3秒触发 |
| L | 最后,只允许在日字段或周字段上,在日字段上使用L表示当月最后一天,在周字段上使用3L表示该月最后一个周二 |
| W | 只允许用在日字段上,表示距离最近的该日的工作日,工作日指的是周一至周五 |

# 只允许在周字段上,表示每月的第几个周几,如2#3 , 每月的第3个周二

说明:

在子表达式(分钟)里的"0/15"表示从第0分钟开始,每15分钟

在子表达式(分钟)里的"3/20"表示从第3分钟开始,每20分钟(它和"3,23,43")的含义一样
"?"字符仅被用于天(月)和天(星期)两个子表达式,表示不指定值

当2个子表达式其中之一被指定了值以后,为了避免冲突,需要将另一个子表达式的值设为"?"
"L" 字符仅被用于天(月)和天(星期)两个子表达式,它是单词"last"的缩写

但是它在两个子表达式里的含义是不同的。

在天(月)子表达式中,"L"表示一个月的最后一天

在天(星期)自表达式中,"L"表示一个星期的最后一天,也就是SAT

如果在"L"前有具体的内容,它就具有其他的含义了

例如:"6L"表示这个月的倒数第6天,"FRIL"表示这个月的最一个星期五

注意:在使用"L"参数时,不要指定列表或范围,因为这会导致问题

4、cron案例

"0 0 10,14,16 * * ?" 每天上午10点,下午2点,4点

"0 0/30 9-17 * * ?" 朝九晚五工作时间内每半小时

"0 0 12 ? * WED" 表示每个星期三中午12点

"0 0 12 * * ?" 每天中午12点触发

"0 15 10 ? * *" 每天上午10:15触发

"0 15 10 * * ?" 每天上午10:15触发

"0 15 10 * * ? *" 每天上午10:15触发

"0 15 10 * * ? 2005" 2005年的每天上午10:15触发

"0 * 14 * * ?" 在每天下午2点到下午2:59期间的每1分钟触发

"0 0/5 14 * * ?" 在每天下午2点到下午2:55期间的每5分钟触发

"0 0/5 14,18 * * ?" 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发

"0 0-5 14 * * ?" 在每天下午2点到下午2:05期间的每1分钟触发

"0 10,44 14 ? 3 WED" 每年三月的星期三的下午2:10和2:44触发

"0 15 10 ? * MON-FRI" 周一至周五的上午10:15触发

"0 15 10 15 * ?" 每月15日上午10:15触发

"0 15 10 L * ?" 每月最后一日的上午10:15触发

"0 15 10 ? * 6L" 每月的最后一个星期五上午10:15触发

"0 15 10 ? * 6#3" 每月的第三个星期五上午10:15触发

5、实现多任务并行(默认单线程)

默认情况下如果存在多个定时任务方法是单线程同步执行

验证代码:创建两个定时任务,其中一个睡眠3秒钟

java 复制代码
@Component
@Slf4j
public class TaskDemo {

    @Scheduled(cron = "* * * * * *")//每秒执行一次
    private void doTask(){
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        log.info("执行定时任务1:" + new Date());
    }

    @Scheduled(cron = "* * * * * *")//每秒执行一次
    private void doTask2(){
        log.info("执行定时任务【2】:" + new Date());
    }
}

单线程验证结果:从执行时间和线程名可以看出两个定时任务是单线程,使用的是同一个线程执行的

增加配置类:异步任务配置类进行线程池的设置

java 复制代码
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;

import java.util.concurrent.Executor;

/**
 * 异步任务配置类
 * 需要实现SchedulingConfigurer接口
 * 需要实现AsyncConfigurer接口
 */
@Configuration
public class AsynTaskConfig implements AsyncConfigurer,SchedulingConfigurer {

    // 定义池子的容量
    private static final int CRON_POOL_SIZE = 10;

    // 注册定时任务线程池
    @Bean
    public ThreadPoolTaskScheduler getThreadPoolTaskScheduler() {
        ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
        // 初始化线程池
        threadPoolTaskScheduler.initialize();
        // 设置池子容量
        threadPoolTaskScheduler.setPoolSize(CRON_POOL_SIZE);
        return threadPoolTaskScheduler;
    }

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.setTaskScheduler(getThreadPoolTaskScheduler());
    }


    //重写AsyncConfigurer的两个方法(异步的配置)
    @Override
    public Executor getAsyncExecutor() {
        return AsyncConfigurer.super.getAsyncExecutor();
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return AsyncConfigurer.super.getAsyncUncaughtExceptionHandler();
    }
}

异步多线程验证结果:从执行时间和线程名可以看出两个定时任务是多线程

6、局限性(不支持年份设定)

@Scheduled 的 cron 无法指定执行的年份

spring taks 不支持年位定时,它毕竟不是quartz,只是简单的定时框架,比起jdk Timer就加入了线程池而以.

一旦制定到年份,会存在问题,启动项目的时候,会一直报一个错误,大概的意思是你的定时任务将永远不会被执行,导致项目一直启动不了。

错误场景一:年份设置在第7位时报错,cron只支持到六位

错误场景二:年份设置在第6位时报错,cron第六位表示星期,内容范围只支持1-7

相关推荐
P7进阶路2 小时前
72.是否可以把所有Bean都通过Spring容器来管理?(Spring的applicationContext.xml中配置全局扫 描)
xml·java·spring
找了一圈尾巴2 小时前
Wend看源码-Java-Map学习
java·学习·map
罗政2 小时前
PDF书籍《手写调用链监控APM系统-Java版》第4章 SPI服务模块化系统
java·pdf·linq
北欧人写代码2 小时前
javaWeb开发
java
犬余2 小时前
设计模式之享元模式:看19路棋盘如何做到一子千面
java·设计模式·享元模式
老马啸西风2 小时前
NLP 中文拼写检测纠正论文 Automatic-Corpus-Generation
java
时雨h2 小时前
30天面试打卡计划 2024-12-25 26 27 面试题
java·开发语言·数据库
别致的影分身3 小时前
Linux 线程池
java·开发语言·jvm
山山而川粤5 小时前
母婴用品系统|Java|SSM|JSP|
java·开发语言·后端·学习·mysql
m0_748235076 小时前
SpringBoot集成kafka
spring boot·kafka·linq