等待主线程完成Quartz调度程序

5
我有一个使用Quartz Scheduler的Java应用程序,它以SchedulerFactoryBean的形式出现。 main()方法获取应用程序上下文,检索根bean,并开始调度作业。
问题在于Scheduler在其自己的线程中运行,因此当主线程完成提交作业时,它会返回并且Scheduler继续运行。当Scheduler最终完成(甚至如果您明确调用shutdown()),应用程序仍然挂起。
我的两个解决方案:
  1. 跟踪作业/触发器计数,每当向调度程序添加作业时增加它。将一个简单的SchedulerListener附加到调度程序上,每次调用triggerFinalized()时减小此计数,并设置一个while循环,其中包含一个Thread.sleep(),它不断检查计数是否达到0。当它达到0时,它将返回到main()方法并正常退出应用程序。
  2. 使用选项1中的自定义SchedulerListener,在其中跟踪作业计数。对于每次调用jobAdded(),增加计数;对于每次调用triggerFinalized(),减少计数。当计数达到0时,在调度程序上调用shutdown()(或不调用也可以),然后调用System.exit(0)
我已经分别独立实现了这两个功能,所以我知道它们都可以正常工作。问题是它们都很糟糕。无限的while循环轮询一个值?System.exit(0)?太糟糕了。
有没有更好的方法,或者这些真的是我的唯一选择?
编辑:在回家的路上思考这个问题时,我得出结论,这可能是由于我正在使用SchedulerFactoryBean引起的。当Spring初始化应用程序上下文时,它会自动启动-这似乎将其放在主线程的范围之外。如果我选择稍微不同的调度程序,在代码中手动初始化并调用start(),这样会在主线程中运行调度程序,从而阻塞它直到调度程序完成运行所有作业吗?还是我仍然有这个问题?
编辑:这个...http://quartz-scheduler.org/documentation/quartz-2.x/examples/Example1 为了让程序有运行任务的机会,我们将休眠90秒钟。调度程序在后台运行,并应在这90秒钟内触发任务。 显然,这样做不起作用,因为调度程序似乎总是在后台运行。
5个回答

5
在您的SchedulerListener中添加一个仅用于同步和锁定的对象。将其命名为exitLock或其他名称。您的主线程检索调度程序,设置侦听器,提交所有作业,然后在返回之前执行以下操作:
Object exitLock = listener.getExitLock();
synchronized (exitLock) {
    exitLock.wait(); // wait unless notified to terminate
}

每次调用triggerFinalized()时,您的监听器会将待处理作业的计数器减少。一旦所有作业执行完成,您的监听器将关闭调度程序。

if (--pendingJobs == 0)
    scheduler.shutdown(); // notice, we don't notify exit from here

调度程序关闭时,会在侦听器上调用最后一个回调函数,我们会在此处通知主线程终止,因此程序会优雅地退出。

void schedulerShutdown() {
    // scheduler has stopped
    synchronized (exitLock) {
        exitLock.notify(); // notify the main thread to terminate
    }
}

triggerFinalized()中,我们没有通知所有挂起的作业已经完成的原因是,如果调度程序过早关闭并且还有未完成的作业,则会导致主线程挂起。通过响应关闭事件进行通知,我们确保程序成功退出。


这看起来像是我选项#2的更优雅版本。然而,synchronized块让我有些困惑。我知道它们看起来像简单的互斥锁,并且我也知道synchronized关键字的工作原理(请一个线程一个线程地执行),但我不完全清楚这里正在同步什么。我猜这与主线程和调度器线程都持有exitLock对象有关? - Random Human
在我的印象中,调度程序和监听器在同一个线程中。那么监听器有它自己的线程吗? - Random Human
没有监听器只是一段浮动的代码,首先在主线程上下文中运行。然后主线程等待。调度程序线程在关闭调度程序后,在自己的上下文中运行监听器代码并终止。主线程醒来并返回。程序退出。 - Ravi K Thapliyal
我想你的答案是正确的 - 它看起来肯定会奏效。我会在周一试一下...等等,不对,美国假期...那就周二试一下,如果一切顺利,之后就接受它。 - Random Human
是的,但通常我们会进行同步以防止竞态条件,即保护共享状态不变得无效。在这里,侦听器没有共享状态,但我们仍然在锁上进行同步(这是一个简单的对象),因为wait()和notify()只能在已经获取了锁的对象上调用。 - Ravi K Thapliyal
显示剩余3条评论

2
我认为这里可以有另外一种解决方案。
关键点: 1. 当任务上次执行时,context.getNextFireTime()返回null。 2. Scheduler.getCurrentlyExecutingJobs == 1表明这是最后一个执行的任务。
因此,当1和2都为真时,我们可以关闭Scheduler并调用System.exit(0)方法。以下是代码:
监听器:
public class ShutDownListenet implements JobListener {
    @Override
    public String getName () { return "someName";    }
    @Override
    public void jobToBeExecuted (JobExecutionContext context) {}
    @Override
    public void jobExecutionVetoed (JobExecutionContext context) {}

    @Override
    public void jobWasExecuted (JobExecutionContext context, JobExecutionException jobException) {
        try {
            if (context.getNextFireTime() == null && context.getScheduler().getCurrentlyExecutingJobs().size() == 1) {
                context.getScheduler().shutdown();
                System.exit(0);
            }
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
    }
}

主函数中的代码 public static void main (String[] args) { 触发器 trigger = ... 任务 job = ...

    JobListener listener = new ShutDownListenet();  
    scheduler.getListenerManager().addJobListener(listener);

    scheduler.scheduleJob(job, trigger);
}

注意

  1. 我不写synchronized块,但我测试了这个代码与100个并发作业一起运行,它可以正常工作。
  2. 没有在“复杂”的环境中进行测试:集群或RMI。(行为可能会有所不同)。

欢迎任何评论。


1
如果您的Quartz计划/触发器基于数据库,则程序需要一直运行,直到您想要停止它。可以通过以下方式实现。思路是挂钩SchedulerListener并在主线程中等待。您需要挂钩自己的方法以优雅地终止程序,这完全是一个不同的话题。
public static void main(String[] args) {

    AnnotationConfigApplicationContext appContext =  // initialize the your spring app Context

    // register the shutdown hook for JVM
    appContext.registerShutdownHook();

    SchedulerFactoryBean schedulerFactory = appContext.getBean(SchedulerFactoryBean.class);
    scheduler = schedulerFactory.getScheduler();

    final Lock lock = new ReentrantLock();
    final Condition waitCond = lock.newCondition();

    try {
        scheduler.getListenerManager().addSchedulerListener(new SchedulerListener() {

            @Override
            public void jobAdded(JobDetail arg0) {
            }

            @Override
            public void jobDeleted(JobKey arg0) {
            }

            @Override
            public void jobPaused(JobKey arg0) {
            }

            @Override
            public void jobResumed(JobKey arg0) {
            }

            @Override
            public void jobScheduled(Trigger arg0) {
            }

            @Override
            public void jobUnscheduled(TriggerKey arg0) {
            }

            @Override
            public void jobsPaused(String arg0) {
            }

            @Override
            public void jobsResumed(String arg0) {
            }

            @Override
            public void schedulerError(String arg0, SchedulerException arg1) {
            }

            @Override
            public void schedulerInStandbyMode() {
            }

            @Override
            public void schedulerShutdown() {
                lock.lock();
                try {
                    waitCond.signal();
                }
                finally {
                    lock.unlock();
                }
            }

            @Override
            public void schedulerShuttingdown() {
            }

            @Override
            public void schedulerStarted() {
            }

            @Override
            public void schedulerStarting() {
            }

            @Override
            public void schedulingDataCleared() {
            }

            @Override
            public void triggerFinalized(Trigger arg0) {
            }

            @Override
            public void triggerPaused(TriggerKey arg0) {
            }

            @Override
            public void triggerResumed(TriggerKey arg0) {
            }

            @Override
            public void triggersPaused(String arg0) {
            }

            @Override
            public void triggersResumed(String arg0) {
            }

        });

        // start the scheduler. I set the SchedulerFactoryBean.setAutoStartup(false)
        scheduler.start();

        lock.lock();
        try {
            waitCond.await();
        }
        finally {
            lock.unlock();
        }
    } finally {
        scheduler.shutdown(true);
    }
}

0

如果有助于其他人。我通过添加一个关闭钩子来解决这个问题,该钩子会在从脚本触发 Ctrl-C 或正常终止(15)时触发。创建一个新的线程并每 3 秒轮询 getCurrentlyExecutingJobs().size(),当作业计数器达到零意味着所有作业已完成时退出。

Runtime.getRuntime().addShutdownHook(new Thread(() -> {
    try {
        while (jobScheduler.getScheduler().getCurrentlyExecutingJobs().size() > 0) {
            Thread.sleep(3000);
        }
        jobScheduler.getScheduler().clear();
    } catch (Exception e) {
        e.printStackTrace();
    }
}));

-3
 while (!scheduler.isShutdown())
 {
     Thread.sleep(2L * 1000L);//Choose reasonable sleep time
 }

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