• Java -- 定时任务实现方式


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

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

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

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

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

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

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

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

    1. /**
    2. * 线程等待;实现java.lang.Runnable接口
    3. */
    4. public class ThreadTask {
    5. public static void main(String[] args) {
    6. final long timeInterval = 1000;
    7. //创建线程(匿名内部类方式)
    8. Thread thread = new Thread(new Runnable() {
    9. @Override
    10. public void run() {
    11. while (true){
    12. SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
    13. String dateStr = sdf.format(new Date());
    14. System.out.println("线程等待实现定时任务:" + dateStr);
    15. try {
    16. Thread.sleep(timeInterval);
    17. } catch (InterruptedException e) {
    18. e.printStackTrace();
    19. }
    20. }
    21. }
    22. });
    23. //开启线程
    24. thread.start();
    25. }
    26. }

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

    1. /**
    2. * 线程等待;自定义类实现java.lang.Runnable接口
    3. */
    4. public class ThreadTask1 {
    5. public static void main(String[] args) {
    6. //自定义类实现java.lang.Runnable接口
    7. MyRunnable runnable = new MyRunnable();
    8. //创建线程(自定义类MyRunnable实现java.lang.Runnable接口)
    9. Thread t = new Thread(runnable);
    10. //开启线程
    11. t.start();
    12. }
    13. }
    14. /**
    15. * 自定义类MyRunnable实现java.lang.Runnable接口
    16. */
    17. class MyRunnable implements Runnable{
    18. final long timeInterval = 1000;
    19. @Override
    20. public void run() {
    21. while (true){
    22. SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
    23. String dateStr = sdf.format(new Date());
    24. System.out.println("线程等待实现定时任务1:" + dateStr);
    25. try {
    26. Thread.sleep(timeInterval);
    27. } catch (InterruptedException e) {
    28. e.printStackTrace();
    29. }
    30. }
    31. }
    32. }

    二、Timer(最古老方式)

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

    1. /**
    2. * Timer是JAVA自带的定时任务类;优点:使用方便
    3. */
    4. public class MyTimerTask {
    5. public static void main(String[] args) {
    6. // 定义一个定时任务
    7. TimerTask timerTask = new TimerTask() {
    8. @Override
    9. public void run() {
    10. SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
    11. String dateStr = sdf.format(new Date());
    12. System.out.println("运行定时任务:" + dateStr);
    13. }
    14. };
    15. // 计时器
    16. Timer timer = new Timer();
    17. // 添加执行任务(延迟 1s 执行,每 3s 执行一次)
    18. timer.schedule(timerTask, 1000, 3000);
    19. }
    20. }

    Timer类核心方法如下:

    1. // 在指定延迟时间后执行指定的任务
    2. schedule(TimerTask task,long delay);
    3. // 在指定时间执行指定的任务。(只执行一次)
    4. schedule(TimerTask task, Date time);
    5. // 延迟指定时间(delay)之后,开始以指定的间隔(period)重复执行指定的任务
    6. schedule(TimerTask task,long delay,long period);
    7. // 在指定的时间开始按照指定的间隔(period)重复执行指定的任务
    8. schedule(TimerTask task, Date firstTime , long period);
    9. // 在指定的时间开始进行重复的固定速率执行任务
    10. scheduleAtFixedRate(TimerTask task,Date firstTime,long period);
    11. // 在指定的延迟后开始进行重复的固定速率执行任务
    12. scheduleAtFixedRate(TimerTask task,long delay,long period);
    13. // 终止此计时器,丢弃所有当前已安排的任务。
    14. cancal();
    15. // 从此计时器的任务队列中移除所有已取消的任务。
    16. purge();

    Timer 缺点分析

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

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

    1. /**
    2. * Timer是JAVA自带的定时任务类;优点:使用方便
    3. */
    4. public class MyTimerTask1 {
    5. public static void main(String[] args) {
    6. // 定时任务1
    7. TimerTask timerTask = new TimerTask() {
    8. @Override
    9. public void run() {
    10. SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
    11. String dateStr = sdf.format(new Date());
    12. System.out.println("进入定时任务1:" + dateStr);
    13. try {
    14. // 休眠 5 秒
    15. TimeUnit.SECONDS.sleep(5);
    16. } catch (InterruptedException e) {
    17. e.printStackTrace();
    18. }
    19. dateStr = sdf.format(new Date());
    20. System.out.println("运行定时任务1:" + dateStr);
    21. }
    22. };
    23. // 定时任务2
    24. TimerTask timerTask2 = new TimerTask() {
    25. @Override
    26. public void run() {
    27. SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
    28. String dateStr = sdf.format(new Date());
    29. System.out.println("运行定时任务2:" + dateStr);
    30. }
    31. };
    32. // 计时器
    33. Timer timer = new Timer();
    34. // 添加执行任务(延迟 1s 执行,每 2s 执行一次)
    35. timer.schedule(timerTask, 1000, 2000);
    36. timer.schedule(timerTask2, 1000, 2000);
    37. }
    38. }

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

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

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

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

    1. /**
    2. * Timer是JAVA自带的定时任务类;优点:使用方便
    3. */
    4. public class MyTimerTask2 {
    5. public static void main(String[] args) {
    6. // 定时任务1
    7. TimerTask timerTask = new TimerTask() {
    8. @Override
    9. public void run() {
    10. SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
    11. String dateStr = sdf.format(new Date());
    12. System.out.println("进入定时任务1:" + dateStr);
    13. //发生异常
    14. int num = 10 / 0;
    15. dateStr = sdf.format(new Date());
    16. System.out.println("运行定时任务1:" + dateStr);
    17. }
    18. };
    19. // 定时任务2
    20. TimerTask timerTask2 = new TimerTask() {
    21. @Override
    22. public void run() {
    23. SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
    24. String dateStr = sdf.format(new Date());
    25. System.out.println("运行定时任务2:" + dateStr);
    26. }
    27. };
    28. // 计时器
    29. Timer timer = new Timer();
    30. // 添加执行任务(延迟 1s 执行,每 2s 执行一次)
    31. timer.schedule(timerTask, 1000, 2000);
    32. timer.schedule(timerTask2, 1000, 2000);
    33. }
    34. }

    Timer 小结

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

    三、ScheduledExecutorService;ScheduledThreadPool

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

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

    注:

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

    1. /**
    2. * ScheduledExecutorService是JAVA 1.5后新增的定时任务接口,它是基于线程池设计的定时任务类,每个调度任务都会分配到线程池中的一个线程去执行(任务是并发执行,互不影响)
    3. * ScheduledExecutorService可以实现Timer具备的所有功能,并解决了 Timer类存在的问题
    4. */
    5. public class MyScheduledExecutorService {
    6. public static void main(String[] args) {
    7. // 创建任务队列;10为线程数量
    8. ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
    9. //执行任务
    10. scheduledExecutorService.scheduleAtFixedRate(() -> {
    11. SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
    12. String dateStr = sdf.format(new Date());
    13. System.out.println("ScheduledExecutorService执行定时任务:" + dateStr);
    14. },1,2, TimeUnit.SECONDS);// 1s 后开始执行,每 2s 执行一次
    15. }
    16. }

    1. public class ScheduledThreadPool {
    2. public static void main(String[] args) {
    3. // 参数代表可以同时执行的定时任务个数
    4. ScheduledExecutorService service = Executors.newScheduledThreadPool(3);
    5. /**
    6. * schedule:延时2秒执行一次任务
    7. */
    8. service.schedule(() -> {
    9. System.out.println("task0-start");
    10. sleep(2);
    11. System.out.println("task0-end");
    12. }, 2, TimeUnit.SECONDS);
    13. /**
    14. * scheduleAtFixedRate:1秒后,每间隔2秒执行一次任务
    15. * 注意,如果任务的执行时间(例如6秒)大于间隔时间,则会等待任务执行结束后直接开始下次任务
    16. */
    17. service.scheduleAtFixedRate(() -> {
    18. System.out.println("task1-start");
    19. sleep(2);
    20. System.out.println("task1-end");
    21. }, 1, 2, TimeUnit.SECONDS);
    22. /**
    23. * scheduleWithFixedDelay:1秒后,每次延时2秒执行一次任务
    24. * 注意,这里是等待上次任务执行结束后,再延时固定时间后开始下次任务
    25. */
    26. service.scheduleWithFixedDelay(() -> {
    27. System.out.println("task2-start");
    28. sleep(2);
    29. System.out.println("task2-end");
    30. }, 1, 2, TimeUnit.SECONDS);
    31. }
    32. private static void sleep(long time) {
    33. try {
    34. TimeUnit.SECONDS.sleep(time);
    35. } catch (InterruptedException e) {
    36. e.printStackTrace();
    37. }
    38. }
    39. }

    ScheduledExecutorService主要有以下4个方法

    1. ScheduledFuture schedule(Runnable command,long delay, TimeUnit unit);
    2. ScheduledFuture schedule(Callable callable,long delay, TimeUnit unit);
    3. ScheduledFuture scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnitunit);
    4. ScheduledFuture scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnitunit);

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

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

    scheduleAtFixedRate方法

    1. public ScheduledFuture scheduleAtFixedRate(Runnable command,
    2. long initialDelay,
    3. long period,
    4. TimeUnit unit);

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

    scheduleWithFixedDelay方法

    1. public ScheduledFuture scheduleWithFixedDelay(Runnable command,
    2. long initialDelay,
    3. long delay,
    4. 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来开启定时任务

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

    2、添加定时任务

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

    1. @Component //@Component用于实例化类,将其类托管给 Spring 容器
    2. public class TaskJobUtil {
    3. /**
    4. * cron表达式:表示每2秒 执行任务
    5. */
    6. @Scheduled(cron = "0/2 * * * * ?")
    7. public void task() {
    8. System.out.println("task0-start");
    9. sleep(5);
    10. System.out.println("task0-end");
    11. }
    12. /**
    13. * fixedRate:每间隔2秒执行一次任务
    14. * 注意,默认情况下定时任务是在同一线程同步执行的,如果任务的执行时间(如5秒)大于间隔时间,则会等待任务执行结束后直接开始下次任务
    15. */
    16. @Scheduled(fixedRate = 2000)
    17. public void task0() {
    18. System.out.println("task0-start");
    19. sleep(5);
    20. System.out.println("task0-end");
    21. }
    22. /**
    23. * fixedDelay:每次延时2秒执行一次任务
    24. * 注意,这里是等待上次任务执行结束后,再延时固定时间后开始下次任务
    25. */
    26. @Scheduled(fixedDelay = 2000)
    27. public void task1() {
    28. System.out.println("task1-start");
    29. sleep(5);
    30. System.out.println("task1-end");
    31. }
    32. /**
    33. * initialDelay:首次任务启动的延时时间
    34. */
    35. @Scheduled(initialDelay = 2000, fixedDelay = 3000)
    36. public void task2() {
    37. System.out.println("task2-start");
    38. sleep(5);
    39. System.out.println("task2-end");
    40. }
    41. private void sleep(long time) {
    42. try {
    43. TimeUnit.SECONDS.sleep(time);
    44. } catch (InterruptedException e) {
    45. e.printStackTrace();
    46. }
    47. }
    48. }

    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

    QuartzJob 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依赖

    1. <dependency>
    2. <groupId>org.springframework.bootgroupId>
    3. <artifactId>spring-boot-starter-quartzartifactId>
    4. <version>2.6.6version>
    5. dependency>

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

    (3)配置文件quartz.properties

    1. #主要分为scheduler、threadPool、jobStore、dataSource等部分
    2. org.quartz.scheduler.instanceId=AUTO
    3. org.quartz.scheduler.instanceName=DefaultQuartzScheduler
    4. #如果您希望Quartz Scheduler通过RMI作为服务器导出本身,则将“rmi.export”标志设置为true
    5. #在同一个配置文件中为'org.quartz.scheduler.rmi.export'和'org.quartz.scheduler.rmi.proxy'指定一个'true'值是没有意义的,如果你这样做'export'选项将被忽略
    6. org.quartz.scheduler.rmi.export=false
    7. #如果要连接(使用)远程服务的调度程序,则将“org.quartz.scheduler.rmi.proxy”标志设置为true。您还必须指定RMI注册表进程的主机和端口 - 通常是“localhost”端口1099
    8. org.quartz.scheduler.rmi.proxy=false
    9. org.quartz.scheduler.wrapJobExecutionInUserTransaction=false
    10. #实例化ThreadPool时,使用的线程类为SimpleThreadPool
    11. org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
    12. #threadCount和threadPriority将以setter的形式注入ThreadPool实例
    13. #并发个数 如果你只有几个工作每天触发几次 那么1个线程就可以,如果你有成千上万的工作,每分钟都有很多工作 那么久需要50-100之间.
    14. #只有1到100之间的数字是非常实用的
    15. org.quartz.threadPool.threadCount=5
    16. #优先级 默认值为5
    17. org.quartz.threadPool.threadPriority=5
    18. #可以是“true”或“false”,默认为false
    19. org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread=true
    20. #在被认为“misfired”(失火)之前,调度程序将“tolerate(容忍)”一个Triggers(触发器)将其下一个启动时间通过的毫秒数。默认值(如果您在配置中未输入此属性)为60000(60秒)
    21. org.quartz.jobStore.misfireThreshold=5000
    22. # 默认存储在内存中,RAMJobStore快速轻便,但是当进程终止时,所有调度信息都会丢失
    23. #org.quartz.jobStore.class=org.quartz.simpl.RAMJobStore
    24. #持久化方式,默认存储在内存中,此处使用数据库方式
    25. org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
    26. #您需要为JobStore选择一个DriverDelegate才能使用。DriverDelegate负责执行特定数据库可能需要的任何JDBC工作
    27. # StdJDBCDelegate是一个使用“vanilla”JDBC代码(和SQL语句)来执行其工作的委托,用于完全符合JDBC的驱动程序
    28. org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
    29. #可以将“org.quartz.jobStore.useProperties”配置参数设置为“true”(默认为false),以指示JDBCJobStore将JobDataMaps中的所有值都作为字符串,
    30. #因此可以作为名称 - 值对存储而不是在BLOB列中以其序列化形式存储更多复杂的对象。从长远来看,这是更安全的,因为您避免了将非String类序列化为BLOB的类版本问题
    31. org.quartz.jobStore.useProperties=true
    32. #表前缀
    33. org.quartz.jobStore.tablePrefix=QRTZ_
    34. #数据源别名,自定义
    35. org.quartz.jobStore.dataSource=qzDS
    36. #使用阿里的druid作为数据库连接池
    37. org.quartz.dataSource.qzDS.connectionProvider.class=org.example.config.DruidPoolingconnectionProvider
    38. org.quartz.dataSource.qzDS.URL=jdbc:mysql://127.0.0.1:3306/test_quartz?characterEncoding=utf8&useSSL=false&autoReconnect=true&serverTimezone=UTC
    39. org.quartz.dataSource.qzDS.user=root
    40. org.quartz.dataSource.qzDS.password=123456
    41. org.quartz.dataSource.qzDS.driver=com.mysql.jdbc.Driver
    42. org.quartz.dataSource.qzDS.maxConnections=10
    43. #设置为“true”以打开群集功能。如果您有多个Quartz实例使用同一组数据库表,则此属性必须设置为“true”,否则您将遇到破坏
    44. #org.quartz.jobStore.isClustered=false

    或者添加定时任务配置类

    1. /**
    2. * 定时任务配置
    3. *
    4. * @author hu.tj
    5. */
    6. @Configuration
    7. public class ScheduleConfig
    8. {
    9. @Bean
    10. public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource)
    11. {
    12. SchedulerFactoryBean factory = new SchedulerFactoryBean();
    13. factory.setDataSource(dataSource);
    14. // quartz参数
    15. Properties prop = new Properties();
    16. prop.put("org.quartz.scheduler.instanceName", "ZM Schedule");
    17. prop.put("org.quartz.scheduler.instanceId", "AUTO");
    18. // 线程池配置
    19. prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool");
    20. prop.put("org.quartz.threadPool.threadCount", "20");
    21. prop.put("org.quartz.threadPool.threadPriority", "5");
    22. // JobStore配置
    23. prop.put("org.quartz.jobStore.class", "org.quartz.impl.jdbcjobstore.JobStoreTX");
    24. // 集群配置
    25. prop.put("org.quartz.jobStore.isClustered", "false");
    26. prop.put("org.quartz.jobStore.clusterCheckinInterval", "15000");
    27. prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime", "1");
    28. prop.put("org.quartz.jobStore.txIsolationLevelSerializable", "true");
    29. // sqlserver 启用
    30. // prop.put("org.quartz.jobStore.selectWithLockSQL", "SELECT * FROM {0}LOCKS UPDLOCK WHERE LOCK_NAME = ?");
    31. prop.put("org.quartz.jobStore.misfireThreshold", "12000");
    32. prop.put("org.quartz.jobStore.tablePrefix", "QRTZ_");
    33. factory.setQuartzProperties(prop);
    34. factory.setSchedulerName("ZMScheduler");
    35. // 延时启动
    36. factory.setStartupDelay(1);
    37. factory.setApplicationContextSchedulerContextKey("applicationContextKey");
    38. // 可选,QuartzScheduler
    39. // 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了
    40. factory.setOverwriteExistingJobs(true);
    41. // 设置自动启动,默认为true
    42. factory.setAutoStartup(true);
    43. return factory;
    44. }
    45. }

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

    也可查看官网:

    Quartz Documentation

    1、定义Job

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

    1. @Service
    2. public class QuartzJobService {
    3. public void taskJob(){
    4. SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
    5. String dateStr = sdf.format(new Date());
    6. System.out.println("job0-start:" + dateStr);
    7. try {
    8. TimeUnit.SECONDS.sleep(5);
    9. } catch (InterruptedException e) {
    10. e.printStackTrace();
    11. }
    12. System.out.println("job0-end:" + dateStr);
    13. }
    14. }

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

    1. public class QuartzJobDetail extends QuartzJobBean {
    2. @Override
    3. protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
    4. SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
    5. String dateStr = sdf.format(new Date());
    6. System.out.println("job1-start:" + dateStr);
    7. try {
    8. TimeUnit.SECONDS.sleep(2);
    9. } catch (InterruptedException e) {
    10. e.printStackTrace();
    11. }
    12. // 获取参数
    13. JobDataMap jobDataMap = jobExecutionContext.getMergedJobDataMap();
    14. String date = jobDataMap.getString("date");
    15. System.out.println("参数:" + date);
    16. System.out.println("job1-end:" + dateStr);
    17. }
    18. }

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

    2、配置JobDetail

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

    1. @Configuration
    2. public class QuartzConfig {
    3. /**
    4. * 配置JobDetail
    5. */
    6. //JobDetail可以使用MethodInvokingJobDetailFactoryBean配置
    7. @Bean
    8. public MethodInvokingJobDetailFactoryBean methodInvokingJobDetailFactoryBean(){
    9. MethodInvokingJobDetailFactoryBean bean = new MethodInvokingJobDetailFactoryBean();
    10. // 指定任务类在IoC容器中的Bean名称
    11. bean.setTargetBeanName("quartzJobService");
    12. // 指定要执行的方法名称
    13. bean.setTargetMethod("taskJob");
    14. return bean;
    15. }
    16. }

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

    1. @Configuration
    2. public class QuartzCronTriggerConfig {
    3. //JobDetail可以使用JobDetailFactoryBean配置
    4. @Bean
    5. public JobDetailFactoryBean jobDetailFactoryBean() {
    6. JobDetailFactoryBean bean = new JobDetailFactoryBean();
    7. // 指定任务类名称
    8. bean.setJobClass(QuartzJobDetail.class);
    9. // 准备参数
    10. JobDataMap jobDataMap = new JobDataMap();
    11. jobDataMap.put("date", "2020-08-08");
    12. // 传递参数
    13. bean.setJobDataMap(jobDataMap);
    14. return bean;
    15. }
    16. }

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

    3、配置Trigger

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

    1. @Configuration
    2. public class QuartzConfig {
    3. @Bean
    4. public SimpleTriggerFactoryBean simpleTriggerFactoryBean() {
    5. SimpleTriggerFactoryBean bean = new SimpleTriggerFactoryBean();
    6. bean.setRepeatCount(10);
    7. bean.setRepeatInterval(2000);
    8. // 关联JobDetail
    9. bean.setJobDetail(methodInvokingJobDetailFactoryBean().getObject());
    10. return bean;
    11. }
    12. }

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

    1. @Configuration
    2. public class QuartzConfig {
    3. @Bean
    4. public CronTriggerFactoryBean cronTriggerFactoryBean() {
    5. CronTriggerFactoryBean bean = new CronTriggerFactoryBean();
    6. bean.setCronExpression("0/2 * * * * ? 2020");
    7. // 关联JobDetail
    8. bean.setJobDetail(jobDetailFactoryBean().getObject());
    9. return bean;
    10. }
    11. }

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

    4、配置Scheduler

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

    1. @Configuration
    2. public class QuartzConfig {
    3. @Bean
    4. public SchedulerFactoryBean schedulerFactoryBean() {
    5. SchedulerFactoryBean bean = new SchedulerFactoryBean();
    6. // 注册两个Trigger
    7. bean.setTriggers(simpleTriggerFactoryBean().getObject(), cronTriggerFactoryBean().getObject());
    8. return bean;
    9. }
    10. }

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

    或者编写了Configuration 配置类

    1. /**
    2. * 第一种:Simple类型
    3. * 将该类标记为配置文件
    4. * 创建 JobDetail
    5. * 创建 SimpleTrigger
    6. */
    7. @Configuration
    8. public class QuartzSimpleTriggerConfig {
    9. /**
    10. * 配置JobDetail
    11. */
    12. //JobDetail可以使用MethodInvokingJobDetailFactoryBean配置
    13. @Bean
    14. public MethodInvokingJobDetailFactoryBean methodInvokingJobDetailFactoryBean(){
    15. MethodInvokingJobDetailFactoryBean bean = new MethodInvokingJobDetailFactoryBean();
    16. // 指定任务类在IoC容器中的Bean名称
    17. bean.setTargetBeanName("quartzJobService");
    18. // 指定要执行的方法名称
    19. bean.setTargetMethod("taskJob");
    20. return bean;
    21. }
    22. //通过SimpleTriggerFactoryBean配置的触发器关联MethodInvokingJobDetailFactoryBean配置的JobDetail
    23. @Bean
    24. public SimpleTriggerFactoryBean simpleTriggerFactoryBean() {
    25. SimpleTriggerFactoryBean bean = new SimpleTriggerFactoryBean();
    26. bean.setRepeatCount(10);
    27. bean.setRepeatInterval(2000);
    28. // 关联JobDetail
    29. bean.setJobDetail(methodInvokingJobDetailFactoryBean().getObject());
    30. return bean;
    31. }
    32. /**
    33. * 配置Scheduler
    34. * */
    35. //通过SchedulerFactoryBean来配置Scheduler,来注册Trigger
    36. @Bean
    37. public SchedulerFactoryBean schedulerFactoryBean() {
    38. SchedulerFactoryBean bean = new SchedulerFactoryBean();
    39. // 注册两个Trigger
    40. bean.setTriggers(simpleTriggerFactoryBean().getObject());
    41. return bean;
    42. }
    43. }
    1. /**
    2. * 第二种:Cron类型
    3. * 将该类标记为配置文件
    4. * 创建 JobDetail
    5. * 创建 CronTrigger
    6. */
    7. @Configuration
    8. public class QuartzCronTriggerConfig {
    9. //JobDetail可以使用JobDetailFactoryBean配置
    10. @Bean
    11. public JobDetailFactoryBean jobDetailFactoryBean() {
    12. JobDetailFactoryBean bean = new JobDetailFactoryBean();
    13. // 指定任务类名称
    14. bean.setJobClass(QuartzJobDetail.class);
    15. // 准备参数
    16. JobDataMap jobDataMap = new JobDataMap();
    17. jobDataMap.put("date", "2020-08-08");
    18. // 传递参数
    19. bean.setJobDataMap(jobDataMap);
    20. return bean;
    21. }
    22. /**
    23. * 配置Trigger
    24. */
    25. //通过CronTriggerFactoryBean配置的触发器关联MethodInvokingJobDetailFactoryBean配置的JobDetail
    26. @Bean
    27. public CronTriggerFactoryBean cronTriggerFactoryBean() {
    28. CronTriggerFactoryBean bean = new CronTriggerFactoryBean();
    29. bean.setCronExpression("0/3 * * * * ? 2022");
    30. // 关联JobDetail
    31. bean.setJobDetail(jobDetailFactoryBean().getObject());
    32. return bean;
    33. }
    34. /**
    35. * 配置Scheduler
    36. * */
    37. //通过SchedulerFactoryBean来配置Scheduler,来注册Trigger
    38. @Bean
    39. public SchedulerFactoryBean schedulerFactoryBean() {
    40. SchedulerFactoryBean bean = new SchedulerFactoryBean();
    41. // 注册两个Trigger
    42. bean.setTriggers(cronTriggerFactoryBean().getObject());
    43. return bean;
    44. }
    45. }

    非SpringBoot版

    1. <dependency>
    2. <groupId>org.quartz-schedulergroupId>
    3. <artifactId>quartzartifactId>
    4. <version>2.3.2version>
    5. dependency>
    6. <dependency>
    7. <groupId>org.quartz-schedulergroupId>
    8. <artifactId>quartz-jobsartifactId>
    9. <version>2.3.2version>
    10. dependency>
    1. public class TaskJob implements Job {
    2. @Override
    3. public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
    4. System.out.println(new Date() + " : 任务「TaskJob 」被执行");
    5. }
    6. }
    1. public class MyScheduler {
    2. public static void main(String[] args) throws SchedulerException {
    3. // 1、创建调度器Scheduler
    4. SchedulerFactory schedulerFactory = new StdSchedulerFactory();
    5. Scheduler scheduler = schedulerFactory.getScheduler();
    6. // 2、创建JobDetail实例,并与PrintJob类绑定(Job执行内容)
    7. JobDetail jobDetail = JobBuilder.newJob(PrintJob.class)
    8. .withIdentity("job", "group").build();
    9. // 3、构建Trigger实例,每隔1s执行一次
    10. Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger", "triggerGroup")
    11. .startNow()//立即生效
    12. .withSchedule(SimpleScheduleBuilder.simpleSchedule()
    13. .withIntervalInSeconds(1)//每隔1s执行一次
    14. .repeatForever()).build();//一直执行
    15. //4、Scheduler绑定Job和Trigger,并执行
    16. scheduler.scheduleJob(jobDetail, trigger);
    17. System.out.println("--------scheduler start ! ------------");
    18. scheduler.start();
    19. }
    20. }

    六、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

  • 相关阅读:
    伪代码实现几种常见的时间复杂度算法
    加速开发容错量子计算应用!PsiQuantum官宣将在英国干大事
    C++ Primer第五版_第十九章习题答案(11~20)
    C++(Qt)软件调试---下载和安装最新版Windbg(16)
    Wireshark - tshark支持iptables提供数据包
    项目质量管理
    【前端必会】HtmlWebpackPlugin 和 SplitChunksPlugin 是什么?
    秦皇岛科学选育新品种 国稻种芯·中国水稻节:河北秸秆变肥料
    定时自动刷新页面油猴脚本
    BatchNormalization:解决神经网络中的内部协变量偏移问题
  • 原文地址:https://blog.csdn.net/MinggeQingchun/article/details/126360682