在主线程中等待Quartz Scheduler完成

时间:2013-05-24 23:24:28

标签: java quartz-scheduler

我有一个Java应用程序,它以SchedulerFactoryBean为幌子使用Quartz Scheduler。 main()方法获取应用程序上下文,检索根bean,并开始调度作业。

问题是Scheduler在自己的线程中运行,所以当主线程完成提交作业时,它会返回并且Scheduler在没有它的情况下继续运行。当调度程序最终完成时(或者即使你明确地调用它上面的shutdown()),应用程序也会永远挂在那里。

我有两个解决方案:

  1. 跟踪作业/触发器计数,每当您将作业添加到计划程序时将其递增。将一个简单的SchedulerListener附加到调度程序,每次调用triggerFinalized()时递减此计数,并在其中设置while循环,其中Thread.sleep()不断检查以查看是否计数达到0.如果是,它将返回main()方法,应用程序将正常退出。
  2. 从选项1中获取自定义SchedulerListener,并跟踪其中的作业计数。每次呼叫增加jobAdded(),每次呼叫递减triggerFinalized()。当计数达到0时,在调度程序上调用shutdown()(或不调用,实际上并不重要),然后调用System.exit(0)
  3. 我已经依次独立实现了这两个,所以我知道它们实际上都有功能。问题是它们都很糟糕。轮询值的无限while循环? System.exit(0)? Bleargh。

    有人有更好的方法,还是这些是我唯一的选择?

    编辑:在回家的路上考虑这个问题时,我得出的结论是,这可能是由于我使用的是SchedulerFactoryBean。当Spring初始化应用程序上下文时,这会自动启动 - 这似乎超出了主线程的范围。如果我使用稍微不同的Scheduler,我手动初始化并在代码中调用start(),那么它是否会在主线程中运行Scheduler,从而阻止它直到Scheduler完成所有作业的运行?或者我还有这个问题吗?

    编辑:...... http://quartz-scheduler.org/documentation/quartz-2.x/examples/Example1

    的儿子
      

    为了让程序有机会运行该作业,我们再睡眠90秒。调度程序在后台运行,并应在90秒内启动作业。

    显然,这不起作用,因为调度程序似乎总是在后台运行。

5 个答案:

答案 0 :(得分: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()中没有通知的原因是,如果调度程序过早关闭而且并非所有作业都已完成,我们就会将主线程挂起。通过响应关闭事件通知我们确保我们的程序成功退出。

答案 1 :(得分:2)

我认为这可以是另一种解决方案。

关键点:

  1. 上次执行任务时context.getNextFireTime()返回null
  2. Scheduler.getCurrentlyExecutingJobs == 1表示这是最后一次执行的工作。
  3. 因此,当第1点和第2点为真时,我们可以关闭调度程序并调用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){         触发器触发器= ...         工作= ...

        JobListener listener = new ShutDownListenet();  
        scheduler.getListenerManager().addJobListener(listener);
    
        scheduler.scheduleJob(job, trigger);
    }
    

    注意

    1. 我没有写synchronized块,但是我用100个concurent工作测试了这段代码,它有效。
    2. 未在“复杂”环境中进行测试:群集或RMI。 (行为可能不同)。
    3. 任何评论都很好。

答案 2 :(得分:0)

如果您的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);
    }
}

答案 3 :(得分:0)

如果它可以帮助别人。我通过添加一个在Ctrl-C上触发的shutdown-hook或从脚本中正常kill(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();
    }
}));

答案 4 :(得分:-2)

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