在这里插入图片描述

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 second
        final long timeInterval = 1000;
        Runnable runnable = new Runnable() {
            @Override
            public 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() {
            @Override
            public 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秒执行一次任务

        // 延迟一段时间后关闭 scheduler
        scheduler.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 {
    @Override
    public 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、创建调度器Scheduler
        SchedulerFactory 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框架简单易用
有前端管理页面
监控和报警支持多种任务调度方式
支持分布式
功能相对简单

在这里插入图片描述

GitHub 加速计划 / xx / xxl-job
27.15 K
10.79 K
下载
xxl-job: 是一个分布式任务调度平台,核心设计目标是开发迅速、学习简单、轻量级、易扩展。
最近提交(Master分支:1 个月前 )
e5d26ba2 - 2 个月前
977ad87b - 2 个月前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐