【xxl-job | 第一篇】Java实现定时任务的六种方法

2024-05-06 09:28

本文主要是介绍【xxl-job | 第一篇】Java实现定时任务的六种方法,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在这里插入图片描述

文章目录

  • 1.Java实现定时任务的六种方法
    • 1.1概述
      • 1.1.1定义
      • 1.1.2作用
      • 1.1.3六种实现方法
    • 1.2线程睡眠
    • 1.3 JDK自带Timer
      • 1.3.1 Timer类的核心方法
      • 1.3.2使用示例
    • 1.4 JDK自带ScheduledExecutorService
      • 1.4.1 ScheduledExecutorService的核心方法
      • 1.4.2使用示例
    • 1.5 Quartz框架实现
      • 1.5.1SpringBoot整合Quartz
        • (1)引入依赖
        • (2)定义执行任务的Job
        • (3)创建Scheduler和Trigger
      • 1.5.2优缺点
    • 1.6 Spring Task
      • 1.6.1基于注解方式
        • (1)启动类上添加@EnableScheduling
        • (2)方法上添加@Scheduled
      • 1.6.2讲解cron表达式
        • (1)cron表达式是什么?
        • (2)每个域可以出现的字符
        • (3)特殊字符的含义
        • (4)举例
      • 1.6.3优缺点
    • 1.7xxl-job
    • 1.8总结

1.Java实现定时任务的六种方法

1.1概述

1.1.1定义

​ 定时任务是指在预定的时间间隔或者特定时间点执行的任务或操作。这些任务通常是在后台自动执行的,无需人工干预。

1.1.2作用

  1. 后台任务处理: 定时任务可以用于处理一些后台任务,如日志记录、报告生成、数据统计等,不会影响前端用户的操作体验,同时可以保证系统正常运行。
  2. 提醒和通知: 定时任务可以用于发送提醒和通知,如定时发送邮件、短信提醒、推送通知等,帮助用户及时了解重要信息。
  3. 资源优化: 定时任务可以在系统空闲时执行一些耗时较长的任务,充分利用系统资源,提高了系统的资源利用率。

1.1.3六种实现方法

  • 线程睡眠
  • JDK自带Timer(线程等待)
  • JDK自带ScheduleExecutorSerivce
  • Quartz框架
  • Spring Task(@Scheduled注解)
  • 分布式任务调度
    • Quartz分布式
    • 轻量级神器(XXL-Job)

1.2线程睡眠

public class Task {public static void main(String[] args) {// run in a secondfinal long timeInterval = 1000;Runnable runnable = new Runnable() {@Overridepublic void run() {while (true) {System.out.println("Hello !!");try {Thread.sleep(timeInterval);} catch (InterruptedException e) {e.printStackTrace();}}}};Thread thread = new Thread(runnable);thread.start();}
}

优点

  1. 简单直观:实现简单,易于理解和上手。
  2. 灵活性:可以根据需要自由地调整任务执行的时间间隔。

缺点

  1. 资源占用:由于是通过循环不断地睡眠来实现定时任务,会占用一个线程资源,如果定时任务比较多,会造成资源浪费。
  2. 定时不精确:由于Thread.sleep()方法并不是完全精确的定时方式,任务的执行时间可能会受到线程调度的影响而产生偏差。
  3. 线程安全性:在多线程环境下,需要注意线程安全性,尤其是在任务执行过程中可能会存在共享资源的情况下,需要考虑线程安全性问题。

1.3 JDK自带Timer

  1. JDK自带的Timer API算是最古老的定时任务实现方式了。
  2. Timer是一种定时器工具,用来在一个后台线程计划执行指定任务。它可以安排任务“执行一次”或者定期“执行多次”。
  3. 在实际的开发当中,经常需要一些周期性的操作,比如每5分钟执行某一操作等。对于这样的操作最方便、高效的实现方式就是使用java.util.Timer工具类。

1.3.1 Timer类的核心方法

// 在指定延迟时间后执行指定的任务,第二个参数的单位为毫秒
schedule(TimerTask task,long delay);// 在指定时间执行指定的任务。(只执行一次)
schedule(TimerTask task, Date time);// 延迟指定时间(delay)之后,开始以指定的间隔(period)重复执行指定的任务
schedule(TimerTask task,long delay,long period);// 在指定的时间开始按照指定的间隔(period)重复执行指定的任务
schedule(TimerTask task, Date firstTime , long period);// 在指定的时间开始进行重复的固定速率执行任务
scheduleAtFixedRate(TimerTask task,Date firstTime,long period);// 在指定的延迟后开始进行重复的固定速率执行任务
scheduleAtFixedRate(TimerTask task,long delay,long period);// 终止此计时器,丢弃所有当前已安排的任务。
cancal()// 从此计时器的任务队列中移除所有已取消的任务。
purge()

1.3.2使用示例

public class TimerTaskExample {public static void main(String[] args) {//TimerTask类,用于定义用执行的任务TimerTask task = new TimerTask() {@Overridepublic void run() {System.out.println("定时任务执行了!");}};Timer timer = new Timer();timer.schedule(task, 5000); // 5秒后执行(延迟)}
}

优点

  1. 简单易用:使用Java的Timer和TimerTask类可以很容易地实现定时任务,代码量较少,易于理解和维护。
  2. 精确度高:由于使用了线程等待的方式,可以比较准确地控制任务的执行时间。
  3. 轻量级:Timer和TimerTask类都是Java标准库中的类,不需要额外引入第三方库,对项目的依赖较小。

缺点

  1. 单线程执行:Timer类内部只有一个线程来执行所有的定时任务,如果其中一个任务执行时间过长,会影响其他任务的执行时间。
  2. 异常处理:如果任务执行过程中发生异常而未被捕获,会导致整个定时任务线程终止,后续的任务将不会执行。
  3. 不适合大规模任务:由于Timer类内部只有一个线程执行所有任务,不适合处理大规模的定时任务,可能会造成任务积压和执行延迟。

1.4 JDK自带ScheduledExecutorService

  1. ScheduledExecutorService是JAVA 1.5后新增的定时任务接口,它是基于线程池设计的定时任务类,每个调度任务都会分配到线程池中的一个线程去执行
  2. 也就是说,任务是并发执行,互不影响。
  3. 需要注意:只有当执行调度任务时,ScheduledExecutorService才会真正启动一个线程,其余时间ScheduledExecutorService都是出于轮询任务的状态。

1.4.1 ScheduledExecutorService的核心方法

ScheduledExecutorService主要有以下4个方法:

ScheduledFuture<?> schedule(Runnable command,long delay, TimeUnit unit);
  • 这个方法用于延迟指定时间后执行一个Runnable任务
  • 参数command是要执行的任务,delay是延迟时间,unit是时间单位。
  • 返回一个ScheduledFuture对象,可以通过该对象取消任务的执行或者获取任务执行的状态。
<V> ScheduledFuture<V> schedule(Callable<V> callable,long delay, TimeUnit unit);
  • 这个方法与前一个类似,不同之处在于它接受一个Callable对象作为任务,可以返回一个结果。
  • 参数和返回值与前一个方法类似,只是任务类型不同。
ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnitunit);
  • 这个方法用于以固定的速率执行任务,即每隔一定的时间执行一次任务无论任务的执行时间是多长
  • 参数command是要执行的任务,initialDelay是首次执行的延迟时间,period是任务执行的周期unit是时间单位。
  • 返回一个ScheduledFuture对象,可以通过该对象取消任务的执行或者获取任务执行的状态。
ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnitunit);
  • 这个方法用于以固定的延迟时间执行任务,即每次任务执行完后,延迟一定时间再执行下一次任务
  • 参数command是要执行的任务,initialDelay是首次执行的延迟时间,delay是每次执行结束到下一次执行开始的延迟时间unit是时间单位。
  • 返回一个ScheduledFuture对象,可以通过该对象取消任务的执行或者获取任务执行的状态。

1.4.2使用示例

public class ScheduledTaskExample {public static void main(String[] args) {ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);Runnable task = () -> {System.out.println("定时任务执行了!");};scheduler.scheduleAtFixedRate(task, 0, 5, TimeUnit.SECONDS); // 每隔5秒执行一次任务// 延迟一段时间后关闭 schedulerscheduler.schedule(() -> scheduler.shutdown(), 30, TimeUnit.SECONDS);}
}

优点

  1. 灵活性:ScheduledExecutorService提供了丰富的调度方法,可以实现定时执行、周期性执行等多种调度方式,灵活性高。
  2. 线程池管理:ScheduledExecutorService使用线程池来管理任务执行线程,可以有效地控制并发执行的任务数量,避免资源浪费。
  3. 异常处理:ScheduledExecutorService提供了良好的异常处理机制,可以捕获任务执行过程中的异常并进行处理。

缺点

  1. 不精确的调度:虽然ScheduledExecutorService可以提供相对准确的调度,但仍然受到系统调度器的影响,可能会存在一定的时间偏差。
  2. 复杂性:相比较简单的定时任务实现方式,使用ScheduledExecutorService需要了解和使用线程池、调度器等概念,相对复杂一些。

1.5 Quartz框架实现

  1. Quartz是Job scheduling(作业调度)领域的一个开源项目,Quartz既可以单独使用也可以跟spring框架整合使用,在实际开发中一般会使用后者。
  2. 使用Quartz可以开发一个或者多个定时任务,每个定时任务可以单独指定执行的时间,例如每隔1小时执行一次、每个月第一天上午10点执行一次、每个月最后一天下午5点执行一次等。

Quartz通常有三部分组成:

  • 调度器(Scheduler)
  • 任务(JobDetail)、
  • 触发器(Trigger,包括SimpleTrigger和CronTrigger)

1.5.1SpringBoot整合Quartz

(1)引入依赖
<dependency><groupId>org.quartz-scheduler</groupId><artifactId>quartz</artifactId><version>2.3.2</version>
</dependency>
<dependency><groupId>org.quartz-scheduler</groupId><artifactId>quartz-jobs</artifactId><version>2.3.2</version>
</dependency>
(2)定义执行任务的Job

这里要实现Quartz提供的Job接口:

public class PrintJob implements Job {@Overridepublic void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {System.out.println(new Date() + " : 任务「PrintJob」被执行。");}
}
(3)创建Scheduler和Trigger
  • 创建Scheduler和Trigger,并执行定时任务:
public class MyScheduler {public static void main(String[] args) throws SchedulerException {// 1、创建调度器SchedulerSchedulerFactory schedulerFactory = new StdSchedulerFactory();Scheduler scheduler = schedulerFactory.getScheduler();// 2、创建JobDetail实例,并与PrintJob类绑定(Job执行内容)JobDetail jobDetail = JobBuilder.newJob(PrintJob.class).withIdentity("job", "group").build();// 3、构建Trigger实例,每隔1s执行一次Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger", "triggerGroup").startNow()//立即生效.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1)//每隔1s执行一次.repeatForever()).build();//一直执行//4、Scheduler绑定Job和Trigger,并执行scheduler.scheduleJob(jobDetail, trigger);System.out.println("--------scheduler start ! ------------");scheduler.start();}
}

在上述代码中,其中Job为Quartz的接口业务逻辑的实现通过实现该接口来实现

  • JobDetail绑定指定的Job,每次Scheduler调度执行一个Job的时候,首先会拿到对应的Job,然后创建该Job实例,再去执行Job中的execute()的内容,任务执行结束后,关联的Job对象实例会被释放,且会被JVM GC清除。

  • Trigger是Quartz的触发器,用于通知Scheduler何时去执行对应Job。SimpleTrigger可以实现在一个指定时间段内执行一次作业任务或一个时间段内多次执行作业任务。

CronTrigger功能非常强大,是基于日历的作业调度,而==SimpleTrigger是精准指定间隔,所以相比SimpleTrigger,CroTrigger更加常用。==CroTrigger是基于Cron表达式的(下面会讲)。

1.5.2优缺点

优点:

  1. 灵活性高:Quartz提供了丰富的调度功能,支持多种触发器类型(SimpleTrigger、CronTrigger等),可以实现几乎任何调度需求,包括简单的定时任务和复杂的调度策略。
  2. 可靠性强:Quartz具有很高的可靠性和稳定性,能够处理应用重启、服务器宕机等异常情况,并保证任务不会丢失或重复执行。
  3. 集群支持:Quartz支持集群部署,多个调度器实例可以通过数据库、JMS等方式进行通信和协调,实现任务的分布式调度和负载均衡。
  4. 与Spring集成:Quartz提供了与Spring框架的良好集成,可以方便地将定时任务与Spring应用无缝结合,利用Spring的依赖注入、事务管理等特性。
  5. 丰富的管理功能:Quartz提供了Web管理界面和JMX接口,方便对任务进行监控、管理和调度。

缺点:

  1. 学习曲线较陡:Quartz框架相对复杂,学习曲线较陡,需要花费一定的时间来熟悉其概念和使用方式。
  2. 配置较繁琐:Quartz的配置相对繁琐,需要配置调度器、触发器、作业等组件,对于初学者而言可能会感到有些复杂。
  3. 依赖较重:Quartz框架相对较大,引入了一些依赖库,可能会增加应用的体积和复杂度。

1.6 Spring Task

  1. 从Spring 3开始,Spring自带了一套定时任务工具Spring-Task,可以把它看成是一个轻量级的Quartz,使用起来十分简单
  2. 除Spring相关的包外不需要额外的包,支持注解和配置文件两种形式。通常情况下在Spring体系内,针对简单的定时任务,可直接使用Spring提供的功能。

1.6.1基于注解方式

(1)启动类上添加@EnableScheduling
(2)方法上添加@Scheduled
  • @Scheduled: 指定方法是定时要执行执行的内容, 放在方法的上面,使用cron表达式
@Component
public class TaskManager {/*** 测试定时任务:秒 分 时 日(月) 月 日(周) 年*/@Scheduled(cron = "0/5 * * * * ?")public void testCron(){System.out.println("定时任务:" + new Date());}
}
  • 接下来启动服务,该方法就会每五秒执行一次

  • 特别要说明的是,方法不是启动马上执行的,而是时间最接近时执行,00、05、10、15…,而不是01、06、11…,同理其他域的时间也是一样。

1.6.2讲解cron表达式

(1)cron表达式是什么?

cron表达式是一个字符串,字符串以5或6个空格隔开分开工6或7个域,每一个域代表一个含义,Cron有如下两种语法
格式:

  • Seconds Minutes Hours DayofMonth Month DayofWeek Year 或
  • Seconds Minutes Hours DayofMonth Month DayofWeek
  • 秒 分 时 日(月) 月 日(周) 年
(2)每个域可以出现的字符
  1. (秒)Seconds: 可出现 , - * / 四个字符,有效范围为0-59的整数
  2. (分)Minutes: 可出现 , - * / 四个字符,有效范围为0-59的整数
  3. (时)Hours: 可出现 , - * / 四个字符,有效范围为0-23的整数
  4. (日-月)DayofMonth: 可出现 , - * / ? L W C八个字符,有效范围为0-31的整数
  5. (月)Month: 可出现 , - * / 四个字符,有效范围为1-12的整数或JAN-DEC
  6. (日-周)DayofWeek: 可出现 , - * / ? L C #四个字符,有效范围为1-7的整数或SUN-SAT两个范围。1表示星期天,2表示星期一, 依次类推
  7. Year: 可出现 , - * / 四个字符,有效范围为1970-2099年
(3)特殊字符的含义
  1. 星字符:表示匹配该域的任意值,假如在Minutes域使用*,即表示每分钟都会触发事件。

  2. ? :只能用在DayofMonth和DayofWeek两个域。它也匹配域的任意值,但实际不会。因为DayofMonth和DayofWeek会相互影响。
    例如想在每月的20日触发调度,不管20日到底是星期几,则只能使用如下写法: 13 13 15 20 * ?,其中最后一位只能用?,而不能使用*,如果使用*表示不管星期几都会触发,实际上并不是这样。

  3. -:表示范围,例如在Minutes域使用5-20,表示从5分到20分钟每分钟触发一次

  4. / :表示起始时间开始触发,然后每隔固定时间触发一次,例如在Minutes域使用5/20,则意味着5分钟触发一次,而25,45等分别触发一次.

  5. , :表示列出枚举值。例如:在Minutes域使用5,20,则意味着在5和20分每分钟触发一次。

  6. L :表示最后,只能出现在DayofWeek和DayofMonth域,如果在DayofWeek域使用5L,意味着在最后的一个星期四触发

  7. W :表示有效工作日(周一到周五),只能出现在DayofMonth域,系统将在离指定日期的最近的有效工作日触发事件。
    例如:在DayofMonth使用5W,如果5日是星期六,则将在最近的工作日:星期五,即4日触发。如果5日是星期天,则在6日触发;
    如果5日在星期一到星期五中的一天,则就在5日触发。另外一点,W的最近寻找不会跨过月份

  8. LW :这两个字符可以连用,表示在某个月最后一个工作日,即最后一个星期五。

    # :用于确定每个月第几个星期几,只能出现在DayofMonth域。例如在4#2,表示某月的第二个星期三。

(4)举例
  • 0 0 2 1 * ? * :每个月的1号,02:00 执行作业
  • 0 15 10 ? * MON-FRI:每个月的周一到周五的,10:15 执行作业
  • 0 15 10 ? * 6L 2002-2006:表示2002-2006年的每个月的最后一个星期五上午10:15执行作业

image-20240505192139158

1.6.3优缺点

  1. 简单易用:Spring Task框架非常简单易用,通过注解或XML配置就可以快速创建定时任务。主要注解包括@Scheduled@EnableScheduling,它们让定时任务的创建和管理变得非常方便。
  2. 与Spring集成良好:Spring Task与Spring框架无缝集成,可以使用Spring的依赖注入、事务管理等特性。此外,它还可以方便地结合其他Spring组件,例如Spring Boot,使定时任务在Spring环境中变得非常简单。
  3. 支持多种调度方式:Spring Task支持基于Cron表达式、固定速率、固定延迟等多种调度方式。这种灵活性可以满足许多定时任务的需求。
  4. 轻量级:相比于像Quartz这样的框架,Spring Task更为轻量级,因为它是Spring框架的一部分,不需要额外的依赖库。这使其适用于不需要复杂调度的应用场景。
  5. 简单的线程池管理:Spring Task可以使用Spring中的线程池配置,方便管理定时任务的并发执行和线程资源。

缺点:

  1. 功能有限:虽然Spring Task提供了基本的定时任务功能,但相比于Quartz等更专业的调度框架,它在任务的高级调度、集群支持、任务持久化等方面相对较弱。
  2. 缺乏任务管理界面:Spring Task没有内置的Web管理界面,无法方便地监控、管理或重新调度任务。这可能会增加管理任务的难度。
  3. 适用范围有限:Spring Task适用于简单的定时任务场景,对于需要高级调度功能、集群支持、任务持久化、任务优先级等更复杂需求的应用,可能不够强大。

1.7xxl-job

XXL-JOB是一个轻量级分布式任务调度平台。特点是平台化,易部署,开发迅速、学习简单、轻量级、易扩展。由调度中心和执行器功能完成定时任务的执行。调度中心负责统一调度,执行器负责接收调度并执行。

后面将围绕xxl-job重点展开

1.8总结

方法序号定时任务实现方法优点缺点
1线程睡眠简单易用
资源占用
定时不准确
2JDK自带Timer简单易用
轻量级
单线程执行
异常处理不好
不适合大规模任务
3JDK自带ScheduledExecutorService灵活性好
线程池管理,可并发
异常处理好
复杂性高
不精确调度
4Quartz框架灵活性好
有前端管理页面
配置繁琐(作业、调度器、触发器)
依赖繁重
5Spring Task框架简单易用
支持多种调度方式
没有前端管理页面
功能有限
6xxl-job框架简单易用
有前端管理页面
监控和报警支持多种任务调度方式
支持分布式
功能相对简单

在这里插入图片描述

这篇关于【xxl-job | 第一篇】Java实现定时任务的六种方法的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/964001

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听