叁只仓鼠的个人博客

果然人类,无法理解

Java定时任务利器:Quartz实战指南

2025-06-26

Quartz 是一个完全由 Java 编写的开源作业调度框架,为在 Java 应用程序中进行作业调度提供了简单却强大的机制。

核心概念

Quartz框架中的三个核心概念分别是Scheduler(调度器)、Trigger(触发器)和Job(任务)。

  • Scheduler:谁来做?怎么做?

    • Scheduler在Quartz中主要负责控制任务的执行,并在任务执行时为其附加设定的数据。在单机程序上,它会选择最合适的线程来执行任务。在分布式应用中,我们可以自定义Scheduler,使其它能通过数据库存储任务数据,并通过为任务加锁的方式来保证任务仅会被执行一次。

  • Trigger:什么时候做?

    • 我们的定时任务可能是需要延迟几秒启动,也可能是在某个时间段内重复执行,或者是根据cron表达式来定制在每天的某个时间执行一次,所有的这些内容都可以通过Trigger来定制。

  • Job:做什么?

    • 创建一个新的类,并使其实现Job接口,然后在execute方法中定制我们任务的具体内容。

要注意这三者之间没有特定的依赖关系,开发者可以根据自己的需求自由组装它们。

创建一个定时任务

了解Quartz的核心概念之后,我们可以通过一个例子来快速上手这个框架。

先从一个最简单的例子开始:每秒钟在控制台输出一次当前的时间。

package cn.hamster3.quartz;

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

import java.text.SimpleDateFormat;
import java.util.Date;

public class QuartzTest {
    private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static void main(String[] args) throws SchedulerException {
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

        scheduler.scheduleJob(
                JobBuilder.newJob(MyJob.class)
                        .build(),
                TriggerBuilder.newTrigger()
                        .withIdentity("trigger")
                        .withSchedule(SimpleScheduleBuilder.repeatSecondlyForever())
                        .build()
        );

        scheduler.start();
    }

    public static class MyJob implements Job {
        @Override
        public void execute(JobExecutionContext context) {
            System.out.println(DATE_FORMAT.format(new Date()));
        }
    }
}

在这段代码中,我们首先使用StdSchedulerFactory.getDefaultScheduler();创建了一个调度器,然后通过scheduler.scheduleJob()方法为这个调度器注册了任务。

我们新建了一个MyJob,使其作为Job接口的实现,并完成了我们的业务内容。

在触发器这一部分,我们使用SimpleScheduleBuilder.repeatSecondlyForever()来指定这个触发器每秒执行一次。

最后,我们使用scheduler.start();来启动调度器,这样我们的任务就会顺利执行了。

2025-06-26 20:48:52:052
2025-06-26 20:48:53:053
2025-06-26 20:48:54:054
2025-06-26 20:48:55:055
2025-06-26 20:48:56:056

调度器的启动顺序并不重要,你也可以先启动调度器,然后再注册任务。

了解触发器

在Quartz中SimpleScheduleBuilder还有更多的其他方法:

  • repeatSecondlyForever()每秒执行一次,无限执行

  • repeatSecondlyForever(x)每x秒执行一次,无限执行

  • repeatSecondlyForTotalCount(x)每秒执行一次,总共执行x次

  • repeatSecondlyForTotalCount(x, y)每y秒执行一次,总共执行x次

  • 还有repeatMinutelyForeverrepeatHourlyForever

除了这些方法以外,Quartz还内置了DailyTimeIntervalScheduleBuilderCalendarIntervalScheduleBuilderCronScheduleBuilder来满足不同的开发需求。另外我们还能使用TriggerBuilder中的startAtendAt来指定一个任务的开始和结束时间,指定之后任务将只在这个时间段内启用。

例如,如果只想要任务在2025-06-26 22:00:002025-06-26 23:00:00之间每3秒执行一次,则可以使用这个方法指定时间:

package cn.hamster3.quartz;

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class QuartzTest {
    private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static void main(String[] args) throws SchedulerException, ParseException {
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

        scheduler.scheduleJob(
                JobBuilder.newJob(MyJob.class)
                        .build(),
                TriggerBuilder.newTrigger()
                        .withIdentity("trigger")
                        // 每 3 秒执行一次
                        .withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(3))
                        // 指定开始时间
                        .startAt(DATE_FORMAT.parse("2025-06-26 22:00:00"))
                        // 指定结束时间
                        .endAt(DATE_FORMAT.parse("2025-06-26 23:00:00"))
                        .build()
        );

        scheduler.start();
    }

    public static class MyJob implements Job {
        @Override
        public void execute(JobExecutionContext context) {
            System.out.println(DATE_FORMAT.format(new Date()));
        }
    }
}

如果希望每个周六、周日的早上8点执行一次任务,则可以使用CronScheduleBuilder来完成:

package cn.hamster3.quartz;

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

import java.text.SimpleDateFormat;
import java.util.Date;

public class QuartzTest {
    private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static void main(String[] args) throws SchedulerException {
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

        scheduler.scheduleJob(
                JobBuilder.newJob(MyJob.class)
                        .build(),
                TriggerBuilder.newTrigger()
                        .withIdentity("trigger")
                        // 更改任务触发
                        .withSchedule(CronScheduleBuilder.cronSchedule("0 0 8 ? * 1,7 *"))
                        .build()
        );

        scheduler.start();
    }

    public static class MyJob implements Job {
        @Override
        public void execute(JobExecutionContext context) {
            System.out.println(DATE_FORMAT.format(new Date()));
        }
    }
}

可以看到代码的其他部分都没有改变,唯一的变动仅仅只是把withSchedule(SimpleScheduleBuilder.repeatSecondlyForever())替换成了withSchedule(CronScheduleBuilder.cronSchedule("0 0 8 * * ?"))

顺便这里也简单介绍一下Quartz中的cron表达式:

Java(Quartz)
*    *    *    *    *    ?    *
-    -    -    -    -    -    -
|    |    |    |    |    |    |
|    |    |    |    |    |    + year [optional]
|    |    |    |    |    +----- day of week (1 - 7) sun,mon,tue,wed,thu,fri,sat
|    |    |    |    +---------- month (1 - 12) OR jan,feb,mar,apr ...
|    |    |    +--------------- day of month (1 - 31)
|    |    +-------------------- hour (0 - 23)
|    +------------------------- min (0 - 59)
+------------------------------ second (0 - 59)

星号(*):表示匹配任意值。例如,* 在分钟字段中表示每分钟都执行。
逗号(,):用于分隔多个值。例如,1,3,5 在小时字段中表示 1 点、3 点和 5 点执行。
斜线(/):用于指定间隔值。例如,*/5 在分钟字段中表示每 5 分钟执行一次。
连字符(-):用于指定范围。例如,10-20 在日期字段中表示从 10 号到 20 号。
问号(?):仅用于日期和星期几字段,表示不指定具体值,通常用于避免冲突。当指定日期时,星期需要设为?,当指定星期时,日期需要设为?。

“L”代表“Last”。当在星期几字段中使用的时候,可以指定给定月份的结构,例如“最后一个星期五”(5L)。在月日字段中,可以指定一个月的最后一天。
星期几字段可以使用“#”,后面必须跟一个介于1和5之间的数字。例如,5#3表示每个月的第三个星期五。

想要了解更多关于Quartz中cron的知识,可以看这篇文章:https://developer.aliyun.com/article/1349827

如果Quartz的内置类都无法完成我们的需求,我们也可以自己实现一个ScheduleBuilder来根据需求定制自己的触发器。

使用任务数据

一些善于思考的读者也许会这样想:如果Job实例的作用仅仅只是“做某些事情”的话,那为什么不直接使用Runnable接口呢?Job接口中这个execute方法接收的JobExecutionContext参数有什么用?

这个问题的答案就是我们在这一小节中要讨论的内容:在Scheduler中注册任务时我们可以使用usingJobData来为任务附加参数,在Job中可以通过JobExecutionContext来获取之前设置的数据。

来看示例代码:

package cn.hamster3.quartz;

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

import java.text.SimpleDateFormat;
import java.util.Date;

public class QuartzTest {
    private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static void main(String[] args) throws SchedulerException {
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

        scheduler.scheduleJob(
                JobBuilder.newJob(MyJob.class)
                        .usingJobData("jobData", "JOB_DATA")
                        .usingJobData("jobVersion", 1)
                        .build(),
                TriggerBuilder.newTrigger()
                        .withIdentity("trigger")
                        .usingJobData("triggerData", "TRIGGER_DATA")
                        .withSchedule(SimpleScheduleBuilder.repeatSecondlyForTotalCount(1, 1))
                        .build()
        );

        scheduler.start();
    }

    public static class MyJob implements Job {
        @Override
        public void execute(JobExecutionContext context) {
            JobDataMap map = context.getMergedJobDataMap();
            System.out.println("time: " + DATE_FORMAT.format(new Date()));
            System.out.println("jobData: " + map.getString("jobData"));
            System.out.println("jobVersion: " + map.getInt("jobVersion"));
            System.out.println("triggerData: " + map.getString("triggerData"));
        }
    }
}

这段代码的输出:

time: 2025-06-26 23:26:17
jobData: JOB_DATA
jobVersion: 1
triggerData: TRIGGER_DATA

当我们使用StdSchedulerFactory创建Scheduler时,其内置的JobFactory会自动扫描并注入数据到我们的Job实例中,如果Job有对应的setter,则数据会调用该方法设置到我们的Job实例中。

package cn.hamster3.quartz;

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

import java.text.SimpleDateFormat;
import java.util.Date;

public class QuartzTest {
    private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static void main(String[] args) throws SchedulerException {
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

        scheduler.scheduleJob(
                JobBuilder.newJob(MyJob.class)
                        .usingJobData("jobData", "JOB_DATA")
                        .usingJobData("jobVersion", 1)
                        .build(),
                TriggerBuilder.newTrigger()
                        .withIdentity("trigger")
                        .usingJobData("triggerData", "TRIGGER_DATA")
                        .withSchedule(SimpleScheduleBuilder.repeatSecondlyForTotalCount(1, 1))
                        .build()
        );

        scheduler.start();
    }

    public static class MyJob implements Job {
        private String jobData;
        private int jobVersion;
        private String triggerData;

        @Override
        public void execute(JobExecutionContext context) {
            System.out.println("time: " + DATE_FORMAT.format(new Date()));
            System.out.println("jobData: " + jobData);
            System.out.println("jobVersion: " + jobVersion);
            System.out.println("triggerData: " + triggerData);
        }

        public void setJobData(String jobData) {
            this.jobData = jobData;
            System.out.println("set jobData");
        }

        public void setJobVersion(int jobVersion) {
            this.jobVersion = jobVersion;
            System.out.println("set jobVersion");
        }

        public void setTriggerData(String triggerData) {
            this.triggerData = triggerData;
            System.out.println("set triggerData");
        }
    }
}

这段代码的输出如下:

set triggerData
set jobData
set jobVersion
time: 2025-06-26 23:30:38
jobData: JOB_DATA
jobVersion: 1
triggerData: TRIGGER_DATA

另外,Quartz为Job准备了两个注解:

  • @DisallowConcurrentExecution:禁止该任务并发执行。当为Job类添加了这个注解时,该Job类在同一时刻只会有一个实例被执行。

  • @PersistJobDataAfterExecution:在任务执行结束后,保存任务对JobDetail中JobDataMap的修改,使得下一次任务执行时可以获取到上一次任务修改的结果。

这两个注解通常是一起使用的,@PersistJobDataAfterExecution可以让我们的任务持久化保存一些数据,而@DisallowConcurrentExecution则能够保证我们的任务不会并发执行,避免了数据冲突。

package cn.hamster3.quartz;

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

import java.text.SimpleDateFormat;
import java.util.Date;

public class QuartzTest {
    private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static void main(String[] args) throws SchedulerException {
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

        scheduler.scheduleJob(
                JobBuilder.newJob(MyJob.class)
                        .build(),
                TriggerBuilder.newTrigger()
                        .withIdentity("trigger")
                        .withSchedule(SimpleScheduleBuilder.repeatSecondlyForTotalCount(3,1))
                        .build()
        );

        scheduler.start();
    }

    @DisallowConcurrentExecution
    @PersistJobDataAfterExecution
    public static class MyJob implements Job {
        private int persistData = 0;

        @Override
        public void execute(JobExecutionContext context) {
            persistData++;
            System.out.println("time: " + DATE_FORMAT.format(new Date()));
            System.out.println("persistData: " + persistData);

            // 必须手动修改 JobDetail 中的 JobDataMap
            // 且必须添加 @PersistJobDataAfterExecution 注解
            // 否则对 JobDataMap 的修改不会被保存
            context.getJobDetail().getJobDataMap().put("persistData", persistData);
        }

        public int getPersistData() {
            // 注意:虽然 Quartz 会自动调用 setter 为 Job 注入数据
            // 但并不会自动调用 getter 保存数据
            System.out.println("get persistData");
            return persistData;
        }

        public void setPersistData(int persistData) {
            this.persistData = persistData;
            System.out.println("set persistData");
        }
    }
}

这段代码的执行结果:

time: 2025-06-26 23:40:29
persistData: 1
time: 2025-06-26 23:40:30
persistData: 2
time: 2025-06-26 23:40:31
persistData: 3

使用JobStore保存任务数据

即使Quartz能够对JobDataMap进行操作,使我们的任务能够持久化保存某些数据,一些读者可能仍然会觉得多此一举:因为我们使用Runnable也同样能够持久化保存数据。

为什么Quartz要单独设计一个Job类出来?这个JobExecutionContext是否还有其他的功能呢?这一小节我们来揭晓答案。

JobStore负责保存Scheduler中的Trigger、Job以及Calendar等。默认情况下Quartz使用的是RAMJobStore,这意味着所有的数据全部都保存在程序的内存中,当程序关闭时,将会丢失所有的调度信息,这在某些项目中是可以接受的、甚至是必要的。

然而,若要在分布式系统中跨节点保存、同步我们的信息,则可以使用JDBC JobStore,它通过JDBC将任务数据保存在数据库中。

要想使用JDBC JobStore,首先我们要创建对应的数据库表格,MySQL的建表语句可以在这里找到:Github

在数据库中创建好对应的表格后,我们需要通过Properties来为Quartz的StdSchedulerFactory设置参数。请查看下面这部分代码:

package cn.hamster3.quartz;

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Properties;

public class QuartzTest {
    private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static void main(String[] args) throws SchedulerException {
        StdSchedulerFactory factory = new StdSchedulerFactory();

        Properties properties = new Properties();

        // 首先,我们需要设置该实例的名称和 id
        properties.setProperty("org.quartz.scheduler.instanceName", "MyClusteredScheduler");
        // id 不能与其他实例冲突,否则会导致运行异常。设置为 AUTO 将会自动生成一个不重复的 id
        properties.setProperty("org.quartz.scheduler.instanceId", "AUTO");

        // 设置 Quartz 运行任务时使用的线程池
        // 参数文档:https://www.quartz-scheduler.org/documentation/quartz-2.3.0/configuration/ConfigThreadPool.html
        properties.setProperty("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool");
        properties.setProperty("org.quartz.threadPool.threadCount", "5");
        properties.setProperty("org.quartz.threadPool.threadPriority", "5");

        // 配置 JobStore
        // 参数文档:https://www.quartz-scheduler.org/documentation/quartz-2.3.0/configuration/ConfigJobStoreTX.html
        properties.setProperty("org.quartz.jobStore.class", "org.quartz.impl.jdbcjobstore.JobStoreTX");
        properties.setProperty("org.quartz.jobStore.driverDelegateClass", "org.quartz.impl.jdbcjobstore.StdJDBCDelegate");
        properties.setProperty("org.quartz.jobStore.useProperties", "false");
        // 指定使用的连接池名称为 myDS
        properties.setProperty("org.quartz.jobStore.dataSource", "myDS");
        properties.setProperty("org.quartz.jobStore.tablePrefix", "QRTZ_");
        // 关键:开启集群模式
        properties.setProperty("org.quartz.jobStore.isClustered", "true");
        // 集群模式下,检查任务间隔时间
        properties.setProperty("org.quartz.jobStore.clusterCheckinInterval", "1000");

        // 为 myDS 连接池进行配置
        // 不同连接池的配置可以查看这里:https://github.com/quartz-scheduler/quartz/wiki/How-to-Use-DB-Connection-Pool
        // 其他参数的文档:https://www.quartz-scheduler.org/documentation/quartz-2.3.0/configuration/ConfigDataSources.html
        properties.setProperty("org.quartz.dataSource.myDS.provider", "hikaricp");
        properties.setProperty("org.quartz.dataSource.myDS.driver", "com.mysql.cj.jdbc.Driver");
        properties.setProperty("org.quartz.dataSource.myDS.URL", "jdbc:mysql://localhost/test");
        properties.setProperty("org.quartz.dataSource.myDS.user", "Test");
        properties.setProperty("org.quartz.dataSource.myDS.password", "Test123..");
        properties.setProperty("org.quartz.dataSource.myDS.maxConnections", "5");
        properties.setProperty("org.quartz.dataSource.myDS.validationQuery", "SELECT 1");

        factory.initialize(properties);
        Scheduler scheduler = factory.getScheduler();
        scheduler.start();

        scheduler.scheduleJob(
                JobBuilder.newJob(MyJob.class)
                        .usingJobData("persistData", 0)
                        .build(),
                TriggerBuilder.newTrigger()
                        .withIdentity("trigger")
                        .withSchedule(SimpleScheduleBuilder.repeatSecondlyForever())
                        .build()
        );
    }

    @DisallowConcurrentExecution
    @PersistJobDataAfterExecution
    public static class MyJob implements Job {
        private int persistData;

        @Override
        public void execute(JobExecutionContext context) {
            persistData++;
            System.out.println("time: " + DATE_FORMAT.format(new Date()));
            System.out.println("persistData: " + persistData);

            // 必须手动修改 JobDetail 中的 JobDataMap
            // 且必须添加 @PersistJobDataAfterExecution 注解
            // 否则对 JobDataMap 的修改不会被保存
            context.getJobDetail().getJobDataMap().put("persistData", persistData);
        }

        public int getPersistData() {
            // 注意:虽然 Quartz 会自动调用 setter 为 Job 注入数据
            // 但并不会自动调用 getter 保存数据
            System.out.println("get persistData");
            return persistData;
        }

        public void setPersistData(int persistData) {
            this.persistData = persistData;
            System.out.println("set persistData");
        }
    }
}

第一次启动该程序时,会像上一个版本那样,persistData从1开始计算。

然而,当我们关掉这个程序并再次启动时,输出会像下面这样:

Exception in thread "main" org.quartz.ObjectAlreadyExistsException: Unable to store Trigger with name: 'trigger' and group: 'DEFAULT', because one already exists with this identification.
	at org.quartz.impl.jdbcjobstore.JobStoreSupport.storeTrigger(JobStoreSupport.java:1179)
	at org.quartz.impl.jdbcjobstore.JobStoreSupport$2.executeVoid(JobStoreSupport.java:1063)
	at org.quartz.impl.jdbcjobstore.JobStoreSupport$VoidTransactionCallback.execute(JobStoreSupport.java:3652)
	at org.quartz.impl.jdbcjobstore.JobStoreSupport$VoidTransactionCallback.execute(JobStoreSupport.java:3650)
	at org.quartz.impl.jdbcjobstore.JobStoreSupport.executeInNonManagedTXLock(JobStoreSupport.java:3736)
	at org.quartz.impl.jdbcjobstore.JobStoreTX.executeInLock(JobStoreTX.java:95)
	at org.quartz.impl.jdbcjobstore.JobStoreSupport.storeJobAndTrigger(JobStoreSupport.java:1058)
	at org.quartz.core.QuartzScheduler.scheduleJob(QuartzScheduler.java:841)
	at org.quartz.impl.StdScheduler.scheduleJob(StdScheduler.java:250)
	at cn.hamster3.quartz.QuartzTest.main(QuartzTest.java:57)
set persistData
time: 2025-06-27 01:06:50
persistData: 19
set persistData
time: 2025-06-27 01:06:51
persistData: 20
set persistData

先有一个报错,这个报错提示我们已经存在一个名称为trigger的触发器。随后,任务会被正常执行,我们可以看到persistData不再是从1开始输出,而是直接来到了19。

这意味着我们的任务、任务数据(也就是persistData)都已经被成功地保存到了数据库中。如果我们删掉scheduler.scheduleJob代码,不再为Scheduler注册任务,它也会去到数据库中寻找已经存在的任务,并运行它们。

package cn.hamster3.quartz;

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Properties;

public class QuartzTest {
    private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static void main(String[] args) throws SchedulerException {
        StdSchedulerFactory factory = new StdSchedulerFactory();

        Properties properties = new Properties();

        // 设置 properties ...

        factory.initialize(properties);
        Scheduler scheduler = factory.getScheduler();
        scheduler.start();

        // 不再手动任务
//        scheduler.scheduleJob(
//                JobBuilder.newJob(MyJob.class)
//                        .usingJobData("persistData", 0)
//                        .build(),
//                TriggerBuilder.newTrigger()
//                        .withIdentity("trigger")
//                        .withSchedule(SimpleScheduleBuilder.repeatSecondlyForever())
//                        .build()
//        );
    }

    @DisallowConcurrentExecution
    @PersistJobDataAfterExecution
    public static class MyJob implements Job {
        private int persistData;

        @Override
        public void execute(JobExecutionContext context) {
            persistData++;
            System.out.println("time: " + DATE_FORMAT.format(new Date()));
            System.out.println("persistData: " + persistData);

            // 必须手动修改 JobDetail 中的 JobDataMap
            // 且必须添加 @PersistJobDataAfterExecution 注解
            // 否则对 JobDataMap 的修改不会被保存
            context.getJobDetail().getJobDataMap().put("persistData", persistData);
        }

        public int getPersistData() {
            // 注意:虽然 Quartz 会自动调用 setter 为 Job 注入数据
            // 但并不会自动调用 getter 保存数据
            System.out.println("get persistData");
            return persistData;
        }

        public void setPersistData(int persistData) {
            this.persistData = persistData;
            System.out.println("set persistData");
        }
    }
}

控制台中仍然会继续输出:

set persistData
time: 2025-06-27 01:12:01
persistData: 337
set persistData
time: 2025-06-27 01:12:02
persistData: 338
set persistData
time: 2025-06-27 01:12:03
persistData: 339

同时,如果我们在保持这个程序不关闭的情况下,再启动一个相同的程序,你会发现新启动的程序中不会运行这个任务,这是因为Quartz会在执行任务前对其进行加锁,确保任务不会重复执行。

当我们把前一个启动的程序关闭后,由于任务锁被释放,后一个程序在得到了锁之后会继续运行这个任务,你会看到控制台再次出现任务的输出。

踩坑提醒

  • Job的实现类的访问权限必须是public,且需要保留一个无参的公开构造方法。否则调度器无法正确实例化任务对象,从而导致任务不被执行。

  • execute方法中仅允许抛出一种类型的异常(包括RuntimeExceptions),即JobExecutionException。因此,你应该将execute方法中的所有内容都放到一个”try-catch”块中。你也应该花点时间看看JobExecutionException的文档,因为你的job可以使用该异常告诉scheduler,你希望如何来处理发生的异常。

如果你想要更深入地学习这个框架,推荐阅读官方文档:https://www.quartz-scheduler.org/documentation/

如果你对英文文档感到恐惧,那么这里有一篇中文文档:https://www.w3cschool.cn/quartz_doc/