quartz:阻止jobs.xml中作业的并发实例

时间:2010-04-20 15:17:04

标签: java concurrency quartz-scheduler

这应该很容易。我正在使用在Apache Tomcat 6.0.18下运行的Quartz,我有一个jobs.xml file来设置我每分钟运行的预定作业。

我想做的是,当下一个触发时间到来时作业仍在运行时,我不想开始新的工作,所以我可以让旧实例完成。

有没有办法在jobs.xml中指定它(防止并发实例)?

如果没有,有没有办法可以在我的应用程序的Job实现中共享对内存中单例的访问权限(这是通过JobExecutionContext吗?)所以我可以自己处理并发? (并检测先前的实例是否正在运行)


更新:在文档中徘徊之后,这是我正在考虑的几种方法,但要么不知道如何让它们工作,要么就会出现问题。

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

    假设触发时间是每分钟,即触发器#0 =在时间0,触发器#1 = 60000毫秒,#2 = 120000,#3 = 180000等,并且触发器#0在时间0触发我的工作需要130000毫秒。使用普通Job,这将执行触发器#1和#2,而作业触发器#0仍在运行。使用StatefulJob,这将按顺序执行触发器#1和#2,紧接在#0结束于130000之后。我不希望这样,我希望#1和#2不运行,并且下一个运行作业的触发器应该发生在#3(180000毫秒)。因此,我仍然需要使用StatefulJob做一些其他工作,让它按照我想要的方式工作,所以我认为使用它没什么好处。

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

    虽然实现接口似乎很简单,但我必须弄清楚如何以声明方式设置TriggerListener的一个实例。 Can't find the docs for the xml file

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

    我不喜欢在Tomcat / Quartz下通过static关键字使用单例的想法,不确定是否有副作用。另外,我真的不希望他们成为真正的单身人士,只是与特定工作定义相关的东西。

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

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

7 个答案:

答案 0 :(得分:60)

还有另一种更简单的解决方案。可以为作业提供DisallowConcurrentExecution注释,以防止多个并发实例在运行。请参阅文档here

链接不断破坏所以这里是相关的样本。

@DisallowConcurrentExecution
public class ColorJob implements Job {

答案 1 :(得分:23)

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;
                }

            }

我们获取当前的工作上下文并检查是否存在具有相同触发器的先前作业实例。如果是这种情况,我们只是跳过返回。

答案 2 :(得分: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
    }

答案 3 :(得分:5)

我完成了类似的工作,使我的工作类实现StatefulJob,这可以确保在当前正在运行的作业完成之前没有其他工作启动。

希望有所帮助;)

PD:我用JBoss实现了它......但我认为这没有任何区别。

答案 4 :(得分:4)

您是否可以将作业设置为StatefulJob,并且对于您创建的每个触发器,将作业的MisfireInstruction设置为在错过时不会触发?不确定您正在使用哪种类型的工作,但您必须对可用于触发类型的misfireInstructions进行一些研究。

谢谢, d

答案 5 :(得分: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);
    }
    ...
}

答案 6 :(得分: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时,scaramouche的解决方案失败了(使用自定义JobFactory类返回单例,而不是为每次执行调用newInstance())