在Java开发中,定时任务是一种十分常见的功能.

定时任务是在约定时间内执行的一段程序

如每天凌晨24点备份同步数据,又或者电商平台 30 分钟后自动取消未支付的订单,每隔一个小时拉取一次数据等都需要使用到定时器

  • 批量处理数据:批量统计上个月的某个数据。
  • 时间驱动的场景:某个时间点发送短信、邮件。
  • 固定频率的场景:每隔5分钟需要执行一次

在Java中,实现定时任务的方式有很多,最简单的在线程中通过JDK自带TimerThread.sleep睡眠线程,或者采用SpringBoot中的@Schedule注解,或者采用定时线程池ScheduledExecutorService来实现,又或者采用Spring Boot中集成Quartz框架实现

一、Thread线程等待(最原始最简单方式)

创建一个thread,然后让它在while循环里一直运行着,通过sleep方法来达到定时任务的效果

匿名内部类实现 java.lang.Runnable 接口

/**
 * 线程等待;实现java.lang.Runnable接口
 */
public class ThreadTask {
    public static void main(String[] args) {
        final long timeInterval = 1000;

        //创建线程(匿名内部类方式)
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
                    String dateStr = sdf.format(new Date());
                    System.out.println("线程等待实现定时任务:" + dateStr);

                    try {
                        Thread.sleep(timeInterval);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        //开启线程
        thread.start();
    }
}

自定义类实现 java.lang.Runnable 接口

/**
 * 线程等待;自定义类实现java.lang.Runnable接口
 */
public class ThreadTask1 {
    public static void main(String[] args) {
        //自定义类实现java.lang.Runnable接口
        MyRunnable runnable = new MyRunnable();
        //创建线程(自定义类MyRunnable实现java.lang.Runnable接口)
        Thread t = new Thread(runnable);
        //开启线程
        t.start();
    }
}

/**
 * 自定义类MyRunnable实现java.lang.Runnable接口
 */
class MyRunnable implements Runnable{
    final long timeInterval = 1000;

    @Override
    public void run() {
        while (true){
            SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
            String dateStr = sdf.format(new Date());
            System.out.println("线程等待实现定时任务1:" + dateStr);

            try {
                Thread.sleep(timeInterval);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

二、Timer(最古老方式)

JDK自带的Timer API算是最古老的定时任务实现方式了。Timer是一种定时器工具,使用java.util.Timer工具类。用来在一个后台线程计划执行指定任务。它可以安排任务“执行一次”或者定期“执行多次”。

/**
 * Timer是JAVA自带的定时任务类;优点:使用方便
 */
public class MyTimerTask {
    public static void main(String[] args) {
        // 定义一个定时任务
        TimerTask timerTask = new TimerTask() {
            @Override
            public void run() {
                SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
                String dateStr = sdf.format(new Date());
                System.out.println("运行定时任务:" + dateStr);
            }
        };

        // 计时器
        Timer timer = new Timer();
        // 添加执行任务(延迟 1s 执行,每 3s 执行一次)
        timer.schedule(timerTask, 1000, 3000);
    }
}

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();

Timer 缺点分析

1、任务执行时间长影响其他任务

当一个任务的执行时间过长时,会影响其他任务的调度

/**
 * Timer是JAVA自带的定时任务类;优点:使用方便
 */
public class MyTimerTask1 {
    public static void main(String[] args) {
        // 定时任务1
        TimerTask timerTask = new TimerTask() {
            @Override
            public void run() {
                SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
                String dateStr = sdf.format(new Date());

                System.out.println("进入定时任务1:" + dateStr);
                try {
                    // 休眠 5 秒
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                dateStr = sdf.format(new Date());
                System.out.println("运行定时任务1:" + dateStr);
            }
        };

        // 定时任务2
        TimerTask timerTask2 = new TimerTask() {
            @Override
            public void run() {
                SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
                String dateStr = sdf.format(new Date());

                System.out.println("运行定时任务2:" + dateStr);
            }
        };

        // 计时器
        Timer timer = new Timer();
        // 添加执行任务(延迟 1s 执行,每 2s 执行一次)
        timer.schedule(timerTask, 1000, 2000);
        timer.schedule(timerTask2, 1000, 2000);
    }
}

当任务 1 运行时间超过设定的间隔时间时,任务 2 也会延迟执行 原本任务 1 和任务 2 的执行时间间隔都是 2s,但因为任务 1 执行了 5s,因此任务 2 的执行时间间隔也变成了 10s(和原定时间不符)

2、任务异常影响其他任务

使用 Timer 类实现定时任务时,当一个任务抛出异常,其他任务也会终止运行

Timer线程是不会捕获异常的,如果TimerTask抛出的了未检查异常则会导致Timer线程终止,同时Timer也不会重新恢复线程的执行,它会错误的认为整个Timer线程都会取消。同时,已经被安排单尚未执行的TimerTask也不会再执行了,新的任务也不能被调度

/**
 * Timer是JAVA自带的定时任务类;优点:使用方便
 */
public class MyTimerTask2 {
    public static void main(String[] args) {
        // 定时任务1
        TimerTask timerTask = new TimerTask() {
            @Override
            public void run() {
                SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
                String dateStr = sdf.format(new Date());

                System.out.println("进入定时任务1:" + dateStr);

                //发生异常
                int num = 10 / 0;

                dateStr = sdf.format(new Date());
                System.out.println("运行定时任务1:" + dateStr);
            }
        };

        // 定时任务2
        TimerTask timerTask2 = new TimerTask() {
            @Override
            public void run() {
                SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
                String dateStr = sdf.format(new Date());

                System.out.println("运行定时任务2:" + dateStr);
            }
        };

        // 计时器
        Timer timer = new Timer();
        // 添加执行任务(延迟 1s 执行,每 2s 执行一次)
        timer.schedule(timerTask, 1000, 2000);
        timer.schedule(timerTask2, 1000, 2000);
    }
}

Timer 小结

Timer 类实现定时任务的优点是方便,因为它是 JDK 自定的定时任务,但缺点是任务如果执行时间太长或者是任务执行异常,会影响其他任务调度

三、ScheduledExecutorService;ScheduledThreadPool

ScheduledExecutorService 是JAVA 1.5后新增的定时任务接口,它是基于线程池设计的定时任务类,每个调度任务都会分配到线程池中的一个线程去执行(任务是并发执行,互不影响)

ScheduledExecutorService 可以实现Timer具备的所有功能,并解决了 Timer类存在的问题

注:

只有当执行调度任务时,ScheduledExecutorService才会真正启动一个线程,其余时间ScheduledExecutorService都是出于轮询任务的状态

/**
 * ScheduledExecutorService是JAVA 1.5后新增的定时任务接口,它是基于线程池设计的定时任务类,每个调度任务都会分配到线程池中的一个线程去执行(任务是并发执行,互不影响)
 * ScheduledExecutorService可以实现Timer具备的所有功能,并解决了 Timer类存在的问题
 */
public class MyScheduledExecutorService {
    public static void main(String[] args) {
        // 创建任务队列;10为线程数量
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);

        //执行任务
        scheduledExecutorService.scheduleAtFixedRate(() -> {
            SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
            String dateStr = sdf.format(new Date());
            System.out.println("ScheduledExecutorService执行定时任务:" + dateStr);
        },1,2, TimeUnit.SECONDS);// 1s 后开始执行,每 2s 执行一次
    }
}

public class ScheduledThreadPool {
    public static void main(String[] args) {
        // 参数代表可以同时执行的定时任务个数
        ScheduledExecutorService service = Executors.newScheduledThreadPool(3);

        /**
         * schedule:延时2秒执行一次任务
         */
        service.schedule(() -> {
            System.out.println("task0-start");
            sleep(2);
            System.out.println("task0-end");
        }, 2, TimeUnit.SECONDS);

        /**
         * scheduleAtFixedRate:1秒后,每间隔2秒执行一次任务
         * 注意,如果任务的执行时间(例如6秒)大于间隔时间,则会等待任务执行结束后直接开始下次任务
         */
        service.scheduleAtFixedRate(() -> {
            System.out.println("task1-start");
            sleep(2);
            System.out.println("task1-end");
        }, 1, 2, TimeUnit.SECONDS);

        /**
         * scheduleWithFixedDelay:1秒后,每次延时2秒执行一次任务
         * 注意,这里是等待上次任务执行结束后,再延时固定时间后开始下次任务
         */
        service.scheduleWithFixedDelay(() -> {
            System.out.println("task2-start");
            sleep(2);
            System.out.println("task2-end");
        }, 1, 2, TimeUnit.SECONDS);
    }

    private static void sleep(long time) {
        try {
            TimeUnit.SECONDS.sleep(time);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

ScheduledExecutorService主要有以下4个方法

ScheduledFuture<?> schedule(Runnable command,long delay, TimeUnit unit);
<V> ScheduledFuture<V> schedule(Callable<V> callable,long delay, TimeUnit unit);
ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnitunit);
ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnitunit);

scheduleAtFixedRatescheduleWithFixedDelay在实现定时程序时比较方便,运用的也比较多。

ScheduledExecutorService中定义的这四个接口方法和Timer中对应的方法几乎一样,只不过Timerscheduled方法需要在外部传入一个TimerTask的抽象任务。 而ScheduledExecutorService封装的更加细致了,传RunnableCallable内部都会做一层封装,封装一个类似TimerTask的抽象任务类(ScheduledFutureTask)。然后传入线程池,启动线程去执行该任务

scheduleAtFixedRate方法

public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
    long initialDelay,
    long period,
    TimeUnit unit);

command为被执行的线程;initialDelay为初始化后延时执行时间;period为两次开始执行最小间隔时间;unit为计时单位

scheduleWithFixedDelay方法

public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
    long initialDelay,
    long delay,
    TimeUnit unit);

command为被执行的线程;initialDelay为初始化后延时执行时间;period为前一次执行结束到下一次执行开始的间隔时间(间隔执行延迟时间);unit为计时单位

ScheduledExecutorService;ScheduledThreadPool小结

在单机生产环境下建议使用 ScheduledExecutorService 来执行定时任务,它是 JDK 1.5 之后自带的 API,因此使用起来也比较方便,并且使用 ScheduledExecutorService 来执行任务,不会造成任务间的相互影响

四、Spring Task;@Scheduled

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

如果使用的是 Spring 或 Spring Boot 框架,可以直接使用 Spring Framework 自带的定时任务,使用上述定时任务的实现方式,很难实现设定了具体时间的定时任务,如当我们需要每周五来执行某项任务时,但如果使用 Spring Task 就可轻松的实现

以 Spring Boot 为例,实现定时任务只需两步:

开启定时任务

添加定时任务

1、开启定时任务

如果是在Spring Boot项目中,需要在启动类上添加@EnableScheduling来开启定时任务

@EnableScheduling // 开启定时任务
@SpringBootApplication
public class Job4ScheduledApplication {
    public static void main(String[] args) {
        SpringApplication.run(Job4ScheduledApplication.class, args);
    }
}

2、添加定时任务

定时任务的添加只需要使用 @Scheduled 注解标注即可,如果有多个定时任务可以创建多个 @Scheduled 注解标注的方法

@Component  //@Component用于实例化类,将其类托管给 Spring 容器
public class TaskJobUtil {

    /**
     * cron表达式:表示每2秒 执行任务
     */
    @Scheduled(cron = "0/2 * * * * ?")
    public void task() {
        System.out.println("task0-start");
        sleep(5);
        System.out.println("task0-end");
    }

    /**
     * fixedRate:每间隔2秒执行一次任务
     * 注意,默认情况下定时任务是在同一线程同步执行的,如果任务的执行时间(如5秒)大于间隔时间,则会等待任务执行结束后直接开始下次任务
     */
    @Scheduled(fixedRate = 2000)
    public void task0() {
        System.out.println("task0-start");
        sleep(5);
        System.out.println("task0-end");
    }

    /**
     * fixedDelay:每次延时2秒执行一次任务
     * 注意,这里是等待上次任务执行结束后,再延时固定时间后开始下次任务
     */
    @Scheduled(fixedDelay = 2000)
    public void task1() {
        System.out.println("task1-start");
        sleep(5);
        System.out.println("task1-end");
    }

    /**
     * initialDelay:首次任务启动的延时时间
     */
    @Scheduled(initialDelay = 2000, fixedDelay = 3000)
    public void task2() {
        System.out.println("task2-start");
        sleep(5);
        System.out.println("task2-end");
    }

    private void sleep(long time) {
        try {
            TimeUnit.SECONDS.sleep(time);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

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

Cron 表达式是一个字符串,以5或6个空格隔开,分为6或7个域,每一个域代表一个含义

Cron的表达式被用来配置CronTrigger实例

从左到右分别为:秒、分、时、日期、月份、星期几、年份

具体参考博文: 

https://blog.csdn.net/MinggeQingchun/article/details/125865778

Cron 在线生成 

crontab执行时间计算 - 在线工具 

quartz/Cron/Crontab表达式在线生成工具-BeJSON.com

[秒] [分] [时] [日期] [月] [星期] [秒] [分] [时] [日期] [月] [星期] [年]

*:表示任何时间触发任务 ,

:表示指定的时间触发任务

-:表示一段时间内触发任务

/:表示从哪一个时刻开始,每隔多长时间触发一次任务。

?:表示用于月中的天和周中的天两个子表达式,表示不指定值 

常用表达式例子

  (1)0/2 * * * * ?   表示每2秒 执行任务

  (1)0 0/2 * * * ?    表示每2分钟 执行任务

  (1)0 0 2 1 * ?   表示在每月的1日的凌晨2点调整任务

  (2)0 15 10 ? * MON-FRI   表示周一到周五每天上午10:15执行作业

  (3)0 15 10 ? 6L 2002-2006   表示2002-2006年的每个月的最后一个星期五上午10:15执行作

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

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

  (6)0 0 12 ? * WED    表示每个星期三中午12点 

  (7)0 0 12 * * ?   每天中午12点触发 

  (8)0 15 10 ? * *    每天上午10:15触发 

  (9)0 15 10 * * ?     每天上午10:15触发 

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

  (11)0 15 10 * * ? 2005    2005年的每天上午10:15触发 

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

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

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

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

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

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

  (18)0 15 10 15 * ?    每月15日上午10:15触发 

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

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

  (21)0 15 10 ? * 6L 2002-2005   2002年至2005年的每月的最后一个星期五上午10:15触发 

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

fixedDelay和fixedRate的区别 

fixedRate有一个时刻表的概念,在任务启动时,T1、T2、T3就已经排好了执行的时刻,比如1分、2分、3分,当T1的执行时间大于1分钟时,就会造成T2晚点,当T1执行完时T2立即执行。

fixedDelay比较简单,表示上个任务结束,到下个任务开始的时间间隔。无论任务执行花费多少时间,两个任务间的间隔始终是一致的

注:

Spring Task 本身不支持持久化,也没有推出官方的分布式集群模式,只能靠开发者在业务应用中自己手动扩展实现,无法满足可视化,易配置的需求

基于Spring Task实现定时任务

  • 优点:

    • 不需要依赖外部框架。
    • 简单快速实现任务。@EnableScheduling@Scheduled 注解
  • 缺点:

    • 无法管理任务。要停止某个任务,必须重新发布。
    • 不支持动态调整。修改任务参数需要重启项目。
    • 不支持集群方式部署。集群模式下会出现任务多次被调度执行的情况,因为集群的节点之间是不会共享任务信息的,每个节点上的任务都会按时执行

五、Quartz

除了JDK自带的API之外,我们还可以使用开源的框架来实现,比如Quartz

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

Quartz架构图如下:

Quartz通常有三部分组成:调度器(Scheduler)、任务(JobDetail)、触发器(Trigger,包括SimpleTriggerCronTrigger

1、Job

定义具体要执行的任务

2、JobDetail

配置要执行任务的描述信息,即如何去定位要执行的Job,每次执行任务时,都会根据JobDetail创建一个Job对象,避免任务并发执行时访问同一个Job对象产生问题

jobdetail 就是对job的定义,而job是具体执行的逻辑内容。 具体的执行的逻辑需要实现 job类,并实现execute方法。如果使用jobdetail来定义,那么每次调度都会创建一个new job实例,这样带来的好处就是任务并发执行的时候,互不干扰,不会对临界资源造成影响

3、Trigger

触发器,配置任务执行的时间规则,需要和一个JobDetail关联起来

在 Quartz 中,trigger 是用于定义 Job 何时执行。当用 Scheduler 注册一个 Job 的时候要创建一个 Trigger 与这个 Job 相关联。

Quartz 中主要提供了四种类型的 Trigger:包括SimpleTrigger、CronTirgger//DateIntervalTrigger和 NthIncludedDayTrigger。这四种 trigger 可以满足企业应用中的绝大部分需求。

最常用的是 SimpleTrigger 和 CronTrigger

一般来说,如果你需要在一个固定的时间和重复次数或者一个固定的间隔时间,那么SimpleTrigger 比较合适; 如果你有许多复杂的作业调度,那么 CronTrigger 比较合适。

CronTrigger 和 Unix 的 cron 机制基本一样,基于通用的公历,我们需要的只是熟悉cron 表达式的用法。

关于Quartz中时间表达式的设置—–corn表达式:

withIdentity() 给触发器一些属性 比如名字,组名

startNow() 立刻启动

withSchedule(ScheduleBuilder schedBuilder) 以某种触发器触发

usingJobData(String dataKey, Boolean value) 给具体job传递参数

4、Scheduler

调度器,它维护了一个JobDetailTrigger的注册表,当任务关联的触发器到达预定的时间,调度器会去执行任务

Scheduler代表一个Quartz的独立运行容器,Trigger和JobDetail可以注册到Scheduler中,两者在Scheduler中拥有各自的组及名称,组及名称是Scheduler查找定位容器中某一对象的依据,Trigger的组及名称必须唯一,JobDetail的组和名称也必须唯一(但可以和Trigger的组和名称相同,因为它们是不同类型的)。

Scheduler定义了多个接口方法,允许外部通过组及名称访问和控制容器中Trigger和JobDetail。 Scheduler可以将Trigger绑定到某一JobDetail中,这样当Trigger触发时,对应的Job就被执行,例如:如schedulerTest.scheduleJob(jobTest, triggerTest)。一个Job可以对应多个Trigger,但一个Trigger只能对应一个Job。

Scheduler拥有一个SchedulerContext,它类似于ServletContext,保存着Scheduler上下文信息,Job和Trigger都可以访问SchedulerContext内的信息。SchedulerContext内部通过一个Map,以键值对的方式维护这些上下文数据,SchedulerContext为保存和获取数据提供了多个put()和getXxx()的方法。可以通过Scheduler# getContext()获取对应的SchedulerContext实例。

scheduler 由 scheduler 工厂创建:包括DirectSchedulerFactory 和 StdSchedulerFactory(STD:standard标准的意思)。 第二种工厂 StdSchedulerFactory 使用较多,因为 DirectSchedulerFactory 使用起来不够方便,需要作许多详细的手工编码设置。

scheduler 主要有三种:RemoteMBeanScheduler, RemoteScheduler 和 StdScheduler。

scheduler 除了启动外,scheduler 操作包括查询、设置 scheduler 为 standby 模式、继续、停止。 启动scheduler 非常简单,只需要调用 start() 方法即可。只有在scheduler 有实例或standby 模式才能调用start() 方法,一旦调用shutdown() 方法之后就不能再调用start() 方法。

(1)在Spring Boot中集成Quartz需要先添加如下Maven依赖

<!-- Spring Boot中集成Quartz -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
    <version>2.6.6</version>
</dependency>

(2)在启动类添加@EnableScheduling注解来开启定时任务 

(3)配置文件quartz.properties

#主要分为scheduler、threadPool、jobStore、dataSource等部分


org.quartz.scheduler.instanceId=AUTO
org.quartz.scheduler.instanceName=DefaultQuartzScheduler
#如果您希望Quartz Scheduler通过RMI作为服务器导出本身,则将“rmi.export”标志设置为true
#在同一个配置文件中为'org.quartz.scheduler.rmi.export'和'org.quartz.scheduler.rmi.proxy'指定一个'true'值是没有意义的,如果你这样做'export'选项将被忽略
org.quartz.scheduler.rmi.export=false
#如果要连接(使用)远程服务的调度程序,则将“org.quartz.scheduler.rmi.proxy”标志设置为true。您还必须指定RMI注册表进程的主机和端口 - 通常是“localhost”端口1099
org.quartz.scheduler.rmi.proxy=false
org.quartz.scheduler.wrapJobExecutionInUserTransaction=false


#实例化ThreadPool时,使用的线程类为SimpleThreadPool
org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
#threadCount和threadPriority将以setter的形式注入ThreadPool实例
#并发个数  如果你只有几个工作每天触发几次 那么1个线程就可以,如果你有成千上万的工作,每分钟都有很多工作 那么久需要50-100之间.
#只有1到100之间的数字是非常实用的
org.quartz.threadPool.threadCount=5
#优先级 默认值为5
org.quartz.threadPool.threadPriority=5
#可以是“true”或“false”,默认为false
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread=true


#在被认为“misfired”(失火)之前,调度程序将“tolerate(容忍)”一个Triggers(触发器)将其下一个启动时间通过的毫秒数。默认值(如果您在配置中未输入此属性)为60000(60秒)
org.quartz.jobStore.misfireThreshold=5000
# 默认存储在内存中,RAMJobStore快速轻便,但是当进程终止时,所有调度信息都会丢失
#org.quartz.jobStore.class=org.quartz.simpl.RAMJobStore

#持久化方式,默认存储在内存中,此处使用数据库方式
org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
#您需要为JobStore选择一个DriverDelegate才能使用。DriverDelegate负责执行特定数据库可能需要的任何JDBC工作
# StdJDBCDelegate是一个使用“vanilla”JDBC代码(和SQL语句)来执行其工作的委托,用于完全符合JDBC的驱动程序
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#可以将“org.quartz.jobStore.useProperties”配置参数设置为“true”(默认为false),以指示JDBCJobStore将JobDataMaps中的所有值都作为字符串,
#因此可以作为名称 - 值对存储而不是在BLOB列中以其序列化形式存储更多复杂的对象。从长远来看,这是更安全的,因为您避免了将非String类序列化为BLOB的类版本问题
org.quartz.jobStore.useProperties=true
#表前缀
org.quartz.jobStore.tablePrefix=QRTZ_
#数据源别名,自定义
org.quartz.jobStore.dataSource=qzDS


#使用阿里的druid作为数据库连接池
org.quartz.dataSource.qzDS.connectionProvider.class=org.example.config.DruidPoolingconnectionProvider
org.quartz.dataSource.qzDS.URL=jdbc:mysql://127.0.0.1:3306/test_quartz?characterEncoding=utf8&useSSL=false&autoReconnect=true&serverTimezone=UTC
org.quartz.dataSource.qzDS.user=root
org.quartz.dataSource.qzDS.password=123456
org.quartz.dataSource.qzDS.driver=com.mysql.jdbc.Driver
org.quartz.dataSource.qzDS.maxConnections=10
#设置为“true”以打开群集功能。如果您有多个Quartz实例使用同一组数据库表,则此属性必须设置为“true”,否则您将遇到破坏
#org.quartz.jobStore.isClustered=false

或者添加定时任务配置类

/**
 * 定时任务配置
 * 
 * @author hu.tj
 */
@Configuration
public class ScheduleConfig
{
    @Bean
    public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource)
    {
        SchedulerFactoryBean factory = new SchedulerFactoryBean();
        factory.setDataSource(dataSource);

        // quartz参数
        Properties prop = new Properties();
        prop.put("org.quartz.scheduler.instanceName", "ZM Schedule");
        prop.put("org.quartz.scheduler.instanceId", "AUTO");
        // 线程池配置
        prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool");
        prop.put("org.quartz.threadPool.threadCount", "20");
        prop.put("org.quartz.threadPool.threadPriority", "5");
        // JobStore配置
        prop.put("org.quartz.jobStore.class", "org.quartz.impl.jdbcjobstore.JobStoreTX");
        // 集群配置
        prop.put("org.quartz.jobStore.isClustered", "false");
        prop.put("org.quartz.jobStore.clusterCheckinInterval", "15000");
        prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime", "1");
        prop.put("org.quartz.jobStore.txIsolationLevelSerializable", "true");

        // sqlserver 启用
        // prop.put("org.quartz.jobStore.selectWithLockSQL", "SELECT * FROM {0}LOCKS UPDLOCK WHERE LOCK_NAME = ?");
        prop.put("org.quartz.jobStore.misfireThreshold", "12000");
        prop.put("org.quartz.jobStore.tablePrefix", "QRTZ_");
        factory.setQuartzProperties(prop);

        factory.setSchedulerName("ZMScheduler");
        // 延时启动
        factory.setStartupDelay(1);
        factory.setApplicationContextSchedulerContextKey("applicationContextKey");
        // 可选,QuartzScheduler
        // 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了
        factory.setOverwriteExistingJobs(true);
        // 设置自动启动,默认为true
        factory.setAutoStartup(true);

        return factory;
    }
}

关于配置详细解释:任务调度框架Quartz(五)Quartz任务调度框架之最全Quartz系统参数配置详解_青山师的博客-CSDN博客

也可查看官网:

Quartz Documentation

1、定义Job

定义Job有两种方式,
第一种是直接定义任务类,并注册到Spring IoC容器中:

@Service
public class QuartzJobService {
    public void taskJob(){
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
        String dateStr = sdf.format(new Date());

        System.out.println("job0-start:" + dateStr);
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("job0-end:" + dateStr);
    }
}

第二种是继承QuartzJobBean,重写executeInternal方法,这种方式可以接受JobDetail传递的参数

public class QuartzJobDetail extends QuartzJobBean {
    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
        String dateStr = sdf.format(new Date());

        System.out.println("job1-start:" + dateStr);
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 获取参数
        JobDataMap jobDataMap = jobExecutionContext.getMergedJobDataMap();
        String date = jobDataMap.getString("date");
        System.out.println("参数:" + date);
        System.out.println("job1-end:" + dateStr);
    }
}

这样就把JobDetail和我们之前定义的QuartzJob关联起来了。

2、配置JobDetail

JobDetail可以使用MethodInvokingJobDetailFactoryBean或者JobDetailFactoryBean配置,配置工作需要在一个Spring配置类中完成,可以定义一个QuartzConfig配置类,首先看MethodInvokingJobDetailFactoryBean的使用:

@Configuration
public class QuartzConfig {
    /**
     * 配置JobDetail
     */
    //JobDetail可以使用MethodInvokingJobDetailFactoryBean配置
    @Bean
    public MethodInvokingJobDetailFactoryBean methodInvokingJobDetailFactoryBean(){
        MethodInvokingJobDetailFactoryBean bean = new MethodInvokingJobDetailFactoryBean();
        // 指定任务类在IoC容器中的Bean名称
        bean.setTargetBeanName("quartzJobService");
        // 指定要执行的方法名称
        bean.setTargetMethod("taskJob");
        return bean;
    }
}

这样就把JobDetail和之前QuartzJob所定义的任务关联起来了,接下来看JobDetailFactoryBean

@Configuration
public class QuartzCronTriggerConfig {
    //JobDetail可以使用JobDetailFactoryBean配置
    @Bean
    public JobDetailFactoryBean jobDetailFactoryBean() {
        JobDetailFactoryBean bean = new JobDetailFactoryBean();
        // 指定任务类名称
        bean.setJobClass(QuartzJobDetail.class);
        // 准备参数
        JobDataMap jobDataMap = new JobDataMap();
        jobDataMap.put("date", "2020-08-08");
        // 传递参数
        bean.setJobDataMap(jobDataMap);
        return bean;
    }
}

这样就把JobDetail和之前定义的QuartzJob2关联起来了,同时传递了参数。

3、配置Trigger

Trigger同样定义在QuartzConfig配置类里,常用的TriggerSimpleTriggerCronTrigger等,它们分别可以通过SimpleTriggerFactoryBeanCronTriggerFactoryBean来完成配置,我们先用SimpleTriggerFactoryBean配置的触发器关联MethodInvokingJobDetailFactoryBean配置的JobDetail

@Configuration
public class QuartzConfig {
    @Bean
    public SimpleTriggerFactoryBean simpleTriggerFactoryBean() {
        SimpleTriggerFactoryBean bean = new SimpleTriggerFactoryBean();
        bean.setRepeatCount(10);
        bean.setRepeatInterval(2000);
        // 关联JobDetail
bean.setJobDetail(methodInvokingJobDetailFactoryBean().getObject());
        return bean;
    }
}

SimpleTriggerFactoryBean的用法比较简单
再用CronTriggerFactoryBean配置的触发器关联JobDetailFactoryBean配置的JobDetail

@Configuration
public class QuartzConfig {
    @Bean
    public CronTriggerFactoryBean cronTriggerFactoryBean() {
        CronTriggerFactoryBean bean = new CronTriggerFactoryBean();
        bean.setCronExpression("0/2 * * * * ? 2020");
        // 关联JobDetail
        bean.setJobDetail(jobDetailFactoryBean().getObject());
        return bean;
    }
}

CronTriggerFactoryBean可以实现类似Spring中@Scheduledcron表达式的功能,同时支持了年份的配置。

4、配置Scheduler

最后一步就是通过SchedulerFactoryBean来配置Scheduler,来注册Trigger

@Configuration
public class QuartzConfig {
    @Bean
    public SchedulerFactoryBean schedulerFactoryBean() {
        SchedulerFactoryBean bean = new SchedulerFactoryBean();
        // 注册两个Trigger
        bean.setTriggers(simpleTriggerFactoryBean().getObject(), cronTriggerFactoryBean().getObject());
        return bean;
    }
}

Quartz会使用异步线程去执行定时任务,不会出现像@Scheduled中定时任务在同一线程同步执行的情况。

或者编写了Configuration 配置类

/**
 * 第一种:Simple类型
 *   将该类标记为配置文件
 *   创建 JobDetail
 *   创建 SimpleTrigger
 */
@Configuration
public class QuartzSimpleTriggerConfig {
    /**
     * 配置JobDetail
     */
    //JobDetail可以使用MethodInvokingJobDetailFactoryBean配置
    @Bean
    public MethodInvokingJobDetailFactoryBean methodInvokingJobDetailFactoryBean(){
        MethodInvokingJobDetailFactoryBean bean = new MethodInvokingJobDetailFactoryBean();
        // 指定任务类在IoC容器中的Bean名称
        bean.setTargetBeanName("quartzJobService");
        // 指定要执行的方法名称
        bean.setTargetMethod("taskJob");
        return bean;
    }

    //通过SimpleTriggerFactoryBean配置的触发器关联MethodInvokingJobDetailFactoryBean配置的JobDetail
    @Bean
    public SimpleTriggerFactoryBean simpleTriggerFactoryBean() {
        SimpleTriggerFactoryBean bean = new SimpleTriggerFactoryBean();
        bean.setRepeatCount(10);
        bean.setRepeatInterval(2000);
        // 关联JobDetail
        bean.setJobDetail(methodInvokingJobDetailFactoryBean().getObject());
        return bean;
    }

    /**
     * 配置Scheduler
     * */
    //通过SchedulerFactoryBean来配置Scheduler,来注册Trigger
    @Bean
    public SchedulerFactoryBean schedulerFactoryBean() {
        SchedulerFactoryBean bean = new SchedulerFactoryBean();
        // 注册两个Trigger
        bean.setTriggers(simpleTriggerFactoryBean().getObject());
        return bean;
    }
}
/**
 * 第二种:Cron类型
 *   将该类标记为配置文件
 *   创建 JobDetail
 *   创建 CronTrigger
 */
@Configuration
public class QuartzCronTriggerConfig {
    //JobDetail可以使用JobDetailFactoryBean配置
    @Bean
    public JobDetailFactoryBean jobDetailFactoryBean() {
        JobDetailFactoryBean bean = new JobDetailFactoryBean();
        // 指定任务类名称
        bean.setJobClass(QuartzJobDetail.class);
        // 准备参数
        JobDataMap jobDataMap = new JobDataMap();
        jobDataMap.put("date", "2020-08-08");
        // 传递参数
        bean.setJobDataMap(jobDataMap);
        return bean;
    }

    /**
     * 配置Trigger
     */
    //通过CronTriggerFactoryBean配置的触发器关联MethodInvokingJobDetailFactoryBean配置的JobDetail
    @Bean
    public CronTriggerFactoryBean cronTriggerFactoryBean() {
        CronTriggerFactoryBean bean = new CronTriggerFactoryBean();
        bean.setCronExpression("0/3 * * * * ? 2022");
        // 关联JobDetail
        bean.setJobDetail(jobDetailFactoryBean().getObject());
        return bean;
    }

    /**
     * 配置Scheduler
     * */
    //通过SchedulerFactoryBean来配置Scheduler,来注册Trigger
    @Bean
    public SchedulerFactoryBean schedulerFactoryBean() {
        SchedulerFactoryBean bean = new SchedulerFactoryBean();
        // 注册两个Trigger
        bean.setTriggers(cronTriggerFactoryBean().getObject());
        return bean;
    }
}

非SpringBoot版

<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>
public class TaskJob implements Job {
    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        System.out.println(new Date() + " : 任务「TaskJob 」被执行");
    }
}
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();
    }
}

六、Xxl-Job分布式任务调度平台

分布式任务调度平台XXL-JOB 

XXL-JOB是一个分布式任务调度平台,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码并接入多家公司线上产品线,开箱即用

xxl是xxl-job的开发者大众点评的【许雪里】名称的拼音开头

https://blog.csdn.net/MinggeQingchun/article/details/129883009

可参考

https://www.cnblogs.com/oeong/p/16212448.html

Java 定时任务-最简单的3种实现方法_深海呐的博客-CSDN博客_java定时任务

java定时任务_定时任务3种实现方式_IT枫斗者的博客-CSDN博客_java定时任务

https://www.jb51.net/article/226802.htm#_label4

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐