java 在主线程中等待 Quartz 调度器完成
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/16744865/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Wait in Main Thread for Quartz Scheduler to Finish
提问by Random Human
I have a Java application that makes use of a Quartz Scheduler in the guise of a SchedulerFactoryBean. The main()
method gets the application context, retrieves the root bean, and commences scheduling jobs.
我有一个 Java 应用程序,它以 SchedulerFactoryBean 的名义使用 Quartz Scheduler。该main()
方法获取应用程序上下文、检索根 bean 并开始调度作业。
The issue is that the Scheduler runs in its own thread, so when the main thread is done submitting jobs, it returns and the Scheduler goes on without it. When the Scheduler is finally done (or even if you explicitly call shutdown()
on it), the application just hangs there for all eternity.
问题是调度器在它自己的线程中运行,所以当主线程完成提交作业时,它返回并且调度器在没有它的情况下继续运行。当调度程序最终完成时(或者即使您明确调用shutdown()
它),应用程序也会永远挂在那里。
I have two solutions:
我有两个解决方案:
- Keep track of the job/trigger count, incrementing it whenever you add a job to the Scheduler. Attach a simple SchedulerListenerto the Scheduler that decrements this count with every call to
triggerFinalized()
, and set up awhile
loop with aThread.sleep()
inside it that constantly checks to see if the count has hit 0. When it does, it will return up to themain()
method and the application will exit normally. - Take the custom SchedulerListener from option 1, and keep track of the job count inside of it. Increment for every call to
jobAdded()
, and decrement for every call totriggerFinalized()
. When the count hits 0, callshutdown()
on the Scheduler (or not, it doesn't actually matter) and then callSystem.exit(0)
.
- 跟踪作业/触发器计数,每当您将作业添加到调度程序时都会增加它。将一个简单的SchedulerListener附加到 Scheduler,它会在每次调用 时递减此计数
triggerFinalized()
,并在其中设置一个while
循环,该循环Thread.sleep()
不断检查计数是否达到 0。当它达到时,它将返回到main()
方法和应用程序将正常退出。 - 从选项 1 中获取自定义 SchedulerListener,并跟踪其中的作业计数。每次调用增加,每次调用
jobAdded()
减少triggerFinalized()
。当计数达到 0 时,调用shutdown()
Scheduler(或不调用,实际上并不重要),然后调用System.exit(0)
.
I have implemented both of these independently in turn, so I know they both actually function. The problem is that they are both terrible. An infinite while
loop polling a value? System.exit(0)
? Bleargh.
我已经依次独立实现了这两个,所以我知道它们实际上都在起作用。问题是他们两个都很可怕。while
轮询值的无限循环?System.exit(0)
? 布莱尔。
Does someone have a better way, or are these seriously my only options here?
有人有更好的方法吗,或者这些真的是我唯一的选择吗?
Edit:While thinking about this on the way home, I came to the conclusion that this may be caused by the fact that I'm using SchedulerFactoryBean. This auto-starts when Spring initializes the application context - that seems to put it outside the scope of the main thread. If I went with a slightly different Scheduler that I manually initialized and called start()
on in the code, would this run the Scheduler in the main thread, thus blocking it until the Scheduler completes running all jobs? Or would I still have this problem?
编辑:在回家的路上考虑这个时,我得出的结论是,这可能是由于我使用的是 SchedulerFactoryBean。这会在 Spring 初始化应用程序上下文时自动启动 - 这似乎将它置于主线程的范围之外。如果我使用一个稍微不同的调度程序,我手动初始化并start()
在代码中调用,这是否会在主线程中运行调度程序,从而阻止它直到调度程序完成运行所有作业?或者我还会有这个问题吗?
Edit:Son of a...http://quartz-scheduler.org/documentation/quartz-2.x/examples/Example1
编辑:儿子... http://quartz-scheduler.org/documentation/quartz-2.x/examples/Example1
To let the program have an opportunity to run the job, we then sleep for 90 seconds. The scheduler is running in the background and should fire off the job during those 90 seconds.
为了让程序有机会运行作业,我们然后休眠 90 秒。调度程序在后台运行,应该在这 90 秒内关闭作业。
Apparently, that will not work, because the scheduler seems to always run in the background.
显然,这行不通,因为调度程序似乎总是在后台运行。
采纳答案by Ravi Thapliyal
In your SchedulerListener
add an object solely for synchronization and locking. Call it exitLock
or something. You main thread retrieves the scheduler, sets up the listener, submits all the jobs and then just before returning executes
在您SchedulerListener
添加一个仅用于同步和锁定的对象。叫它exitLock
什么的。您的主线程检索调度程序,设置侦听器,提交所有作业,然后在返回之前执行
Object exitLock = listener.getExitLock();
synchronized (exitLock) {
exitLock.wait(); // wait unless notified to terminate
}
On every triggerFinalized()
call your listener decrements the counter for pending jobs. Once all the jobs have finished executing your listener shuts the scheduler down.
在每次triggerFinalized()
调用时,您的侦听器都会递减待处理作业的计数器。一旦所有作业完成执行,您的侦听器将关闭调度程序。
if (--pendingJobs == 0)
scheduler.shutdown(); // notice, we don't notify exit from here
Once the scheduler shuts down it invokes one last callback on the listener where we notify the main thread to terminate and hence the program exits gracefully.
一旦调度程序关闭,它就会调用侦听器上的最后一个回调,我们通知主线程终止,因此程序优雅地退出。
void schedulerShutdown() {
// scheduler has stopped
synchronized (exitLock) {
exitLock.notify(); // notify the main thread to terminate
}
}
The reason we didn't notify in triggerFinalized()
when all the pending jobs were finished is that in case the scheduler was shutdown prematurely and not all the jobs were finished we would have left our main thread hanging. By notifying in response to the shutdown event we make sure our program exits successfully.
我们没有在triggerFinalized()
所有挂起的作业完成时通知的原因是,如果调度程序过早关闭并且并非所有作业都完成,我们会让主线程挂起。通过响应关闭事件通知我们确保我们的程序成功退出。
回答by Cherry
I think here can be another solution.
我认为这可以是另一种解决方案。
Key points:
关键点:
- When task was executed the last time
context.getNextFireTime()
returnsnull
. Scheduler.getCurrentlyExecutingJobs == 1
indicate that it is the last executed job.
- 上次执行任务时
context.getNextFireTime()
返回null
。 Scheduler.getCurrentlyExecutingJobs == 1
表明它是最后执行的作业。
So when point 1 and 2 is true we can shutdown Scheduler and call System.exit(0)
.
Here is the code:
因此,当第 1 点和第 2 点为真时,我们可以关闭 Scheduler 并调用System.exit(0)
. 这是代码:
Listener
听众
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();
}
}
}
Code in the main functionpublic static void main (String[] args) { Trigger trigger = ... Job job = ...
main 函数中的代码public static void main (String[] args) { Trigger trigger = ... Job job = ...
JobListener listener = new ShutDownListenet();
scheduler.getListenerManager().addJobListener(listener);
scheduler.scheduleJob(job, trigger);
}
NOTE
笔记
- I do not write
synchronized
blocks, but I tested this code with 100 concurent jobs, it works. - Did not tested in "complex" enviroment: clusters or RMI. (behavior can be differ).
- 我不写
synchronized
块,但我用 100 个并发作业测试了这段代码,它有效。 - 未在“复杂”环境中测试:集群或 RMI。(行为可能不同)。
Any comments are wellcome.
欢迎提出任何意见。
回答by Stackee007
If your Quartz schedules/triggers are based on the database then you program needs to be alive till you would want to stop it. This can be doable like below. The idea is hook SchedulerListenerand wait in the main thread. You need to hook your own way to terminate the program gracefully which completely a different topic itself.
如果您的 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);
}
}
回答by jbilander
If it helps someone else. I solved this by adding a shutdown-hook that triggers on Ctrl-C or normal kill (15) from script. A new Thread is spawned and polls the getCurrentlyExecutingJobs().size()
every 3 seconds and exits when jobs counter has reached zero meaning all jobs finished.
如果它帮助别人。我通过添加一个关闭挂钩来解决这个问题,该挂钩在 Ctrl-C 或脚本中的正常终止 (15) 上触发。产生一个新线程并getCurrentlyExecutingJobs().size()
每 3 秒轮询一次,并在作业计数器达到零时退出,这意味着所有作业已完成。
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
try {
while (jobScheduler.getScheduler().getCurrentlyExecutingJobs().size() > 0) {
Thread.sleep(3000);
}
jobScheduler.getScheduler().clear();
} catch (Exception e) {
e.printStackTrace();
}
}));
回答by Ravindranath Akila
while (!scheduler.isShutdown())
{
Thread.sleep(2L * 1000L);//Choose reasonable sleep time
}