石英计划:在jobs.xml中防止并发实例的方法

45
这应该很容易。我正在使用运行在Apache Tomcat 6.0.18下的Quartz,并且有一个jobs.xml文件,它设置了每分钟运行的计划任务。
我想做的是,如果作业在下一个触发时间到达时仍在运行,则不希望启动新作业,以便让旧实例完成。
在jobs.xml中是否有一种指定此项(防止并发实例)的方法?
如果没有,是否有一种方法可以在我的应用程序的Job实现内共享对内存单例的访问(通过JobExecutionContext实现?),以便我可以自己处理并发?(并检测先前的实例是否正在运行)

更新: 在文档中摸索了一番后,我考虑了几种方法,但要么不知道如何使它们工作,要么存在问题。

  1. 使用StatefulJob。这可以防止并发访问...但我不确定如果使用它会有什么其他副作用,而且我想避免以下情况:

    假设触发时间为每分钟一次,即触发器#0 = 时间0,触发器#1 = 60000毫秒,#2 = 120000,#3 = 180000,等等。触发器#0在时间0时触发我的工作,需要130000毫秒。对于普通的Job,这将在作业触发器#0仍在运行时执行触发器#1和#2。对于StatefulJob,这将在#0在130000结束时立即按顺序执行触发器#1和#2。我不想要那样,我希望#1和#2不要运行,并且下一个运行作业的触发器应该在#3(180000毫秒)处发生。因此,我仍然需要对StatefulJob进行其他处理,使其按照我想要的方式工作,所以我不认为使用它有多大优势。

  2. 使用TriggerListener来从vetoJobExecution()中返回true。

    虽然实现接口似乎很简单,但我必须弄清楚如何声明性地设置TriggerListener的一个实例。找不到xml文件的文档

  3. 使用static共享线程安全对象(例如信号量或其他)由实现Job的类拥有。

    我不喜欢在Tomcat/Quartz下使用static关键字来使用单例,不确定是否有副作用。而且我真的不想让它们成为真正的单例,只是与特定作业定义相关联的东西。

  4. 实现自己的Trigger,它扩展SimpleTrigger并包含可以运行自己的TriggerListener的共享状态。

    同样,我不知道如何设置XML文件以使用此触发器而不是标准的<trigger><simple>...</simple></trigger>


8
在较新版本的Quartz中,使用@DisallowConcurrentExecution标记实现作业的类可以确保在任何时候只有一个类在运行... - sbrattla
7个回答

68

还有一种更简单的解决方案。可以给作业添加一个注释 DisallowConcurrentExecution,以防止多个并发实例运行。请参见文档此处

链接经常失效,因此这里是相关示例。

@DisallowConcurrentExecution
public class ColorJob implements Job {

12
如果使用DisallowConcurrentExecution注释,当前实例会被排队等待执行,直到上一个实例完成。如果作业实例已经在运行中,有没有一种方法可以放弃当前实例的执行? - Vijay Kansal
2
请注意,此限制仅适用于具有相同JobKey的作业(通常情况下是这样)。即使使用相同的类实现,具有不同JobKeys的作业仍然可以并发执行。 - stenix

25

dimitrisli的回答不完整,所以这里是我的回答。

当Quartz Job唤醒时,它会将其JobExecutionContext返回给您。我假设您想跳过具有相同触发器的作业。

List<JobExecutionContext> jobs = jobExecutionContext.getScheduler().getCurrentlyExecutingJobs();
for (JobExecutionContext job : jobs) {
    if (job.getTrigger().equals(jobExecutionContext.getTrigger()) && !job.getJobInstance().equals(this)) {
        logger.info("There's another instance running, so leaving" + this);
        return;
    }
}

我们获取当前任务的上下文,并检查是否存在具有相同触发器的先前任务实例。如果是这种情况,我们只需使用“return”跳过。


18

当你的Quartz作业唤醒时,你可以这样做:

JobDetail existingJobDetail = sched.getJobDetail(jobName, jobGroup);
if (existingJobDetail != null) {
    List<JobExecutionContext> currentlyExecutingJobs = (List<JobExecutionContext>) sched.getCurrentlyExecutingJobs();
    for (JobExecutionContext jec : currentlyExecutingJobs) {
        if (existingJobDetail.equals(jec.getJobDetail())) {
            // String message = jobName + " is already running.";
            // log.info(message);
            // throw new JobExecutionException(message,false);
        }
    }
    // sched.deleteJob(jobName, jobGroup); if you want to delete the scheduled but not-currently-running job
}

1
看起来很有趣...你需要检查现有的JobExecutionContext,否则你总是会得到异常。此外,需要捕获sched.getCurrentlyExecutingJobs()抛出的异常。 - Jason S

5
我曾经通过让我的工作类实现StatefulJob来实现类似的功能,这可以确保在当前运行的作业完成之前不会启动其他作业。
希望能够帮到你 ;)
附注:我是使用JBoss实现的...但我认为这没有任何区别。

有些帮助,但并不是真正的解决方法,请参见我上面的评论#1。 - Jason S

4

您能将作业设置为StatefulJob,并为每个触发器创建的作业设置MisfireInstruction,以便在错过时不触发吗?不确定您使用的作业类型是什么,但您需要调查可用于您的触发器类型的misfireInstructions。

谢谢, D


3
如果您正在使用org.springframework.scheduling.quartz.QuartzJobBean
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
    try {
        Scheduler scheduler = context.getScheduler();
        List<JobExecutionContext> jobs = scheduler.getCurrentlyExecutingJobs();
        for (JobExecutionContext job : jobs) {
            if (job.getTrigger().equals(context.getTrigger()) && job.getJobDetail() != context.getJobDetail()) {
                LOG.warn("Ignored!");
                return;
            }
        }
        ...
    } catch (SchedulerException e) {
        LOG.error("What a luck! :'(", e);
    }
    ...
}

1
或者简单地在您的QuartzJobBean实现上添加@DisallowConcurrentExecution。 - Sariq Shaikh

1

对scaramouche的解决方案进行了轻微变化。

List<JobExecutionContext> jobs = jobExecutionContext.getScheduler().getCurrentlyExecutingJobs();
for (JobExecutionContext job : jobs) {
    if (job.getTrigger().equals(jobExecutionContext.getTrigger()) && !job.getFireInstanceId().equals(jobExecutionContext.getFireInstanceId()) {
        logger.info("There's another instance running, so leaving" + this);
        return;
    }

}

当所有JobExecutions都使用单个实例时(使用自定义的JobFactory类返回单例,而不是为每个执行调用newInstance()),scaramouche的解决方案将失败。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接