永久运行的任务的Java执行者最佳实践
我正在做一个Java项目,我需要有多个任务asynchronous运行。 我被引导相信Executor是我做这件事的最好方式,所以我对此熟悉了。 (Yay得到报酬学习!)然而,我不清楚什么是最好的方式是完成我想要做的事情。
为了争论,让我们说我有两个任务运行。 预计也不会终止,并且两者都应该在申请期限内运行。 我试图写一个主包装类,使:
- 如果任一任务引发exception,包装器将捕获它并重新启动任务。
- 如果任何一个任务运行完成,包装将通知并重新启动任务。
现在需要注意的是,这两个任务的实现都会将run()
中的代码封装在一个永远不会完成的无限循环中,而try / catch块应该能够处理所有的运行时exception而不会中断循环。 我试图增加另一层确定性; 如果我或者跟随我的人做了一些愚蠢的事情,使得这些保护措施失败,并停止这项任务,那么应用程序需要做出适当的反应。
是否有一个最好的做法来解决这个问题,比我更有经验的人会推荐?
FWIW,我掀起了这个testing课:
public class ExecTest { private static ExecutorService executor = null; private static Future results1 = null; private static Future results2 = null; public static void main(String[] args) { executor = Executors.newFixedThreadPool(2); while(true) { try { checkTasks(); Thread.sleep(1000); } catch (Exception e) { System.err.println("Caught exception: " + e.getMessage()); } } } private static void checkTasks() throws Exception{ if (results1 == null || results1.isDone() || results1.isCancelled()) { results1 = executor.submit(new Test1()); } if (results2 == null || results2.isDone() || results2.isCancelled()) { results2 = executor.submit(new Test2()); } } } class Test1 implements Runnable { public void run() { while(true) { System.out.println("I'm test class 1"); try {Thread.sleep(1000);} catch (Exception e) {} } } } class Test2 implements Runnable { public void run() { while(true) { System.out.println("I'm test class 2"); try {Thread.sleep(1000);} catch (Exception e) {} } } }
它的行为是我想要的,但是我不知道是否有任何疑点,低效率,或者完全错误的头脑,等着让我惊讶。 (事实上,鉴于我是新手,如果没有什么不对或不可取,我会感到震惊的。)
任何见解都欢迎。
在我之前的项目中,我遇到过类似的情况,在我的代码面对一个愤怒的客户时,我的朋友和我加了两个大的保安人员:
- 在无限循环中,也会捕获错误,而不仅仅是exception。 有时候会发生意外的事情,Java会在你身上抛出一个错误,而不是一个例外。
- 使用退避开关,所以如果出现问题并且是不可恢复的,则不要通过急切地启动另一个循环来升级情况。 相反,你需要等到情况恢复正常,然后重新开始。
例如,我们遇到了数据库停机的情况,并且在循环过程中抛出了SQLException。 不幸的结果是,代码再次通过循环,只是再次击中相同的exception,等等。 日志显示,我们在一秒钟内发生了大约300次相同的SQLException! …这种情况偶尔发生几次,偶尔会出现5秒左右的JVM暂停,在此期间,应用程序没有响应,直到最终抛出一个错误并且线程死亡!
所以我们实现了一个退出策略,大概在下面的代码中显示,如果exception是不可恢复的(或在几分钟内恢复),那么我们等待更长时间才能恢复操作。
class Test1 implements Runnable { public void run() { boolean backoff = false; while(true) { if (backoff) { Thread.sleep (TIME_FOR_LONGER_BREAK); backoff = false; } System.out.println("I'm test class 1"); try { // do important stuff here, use database and other critical resources } catch (SqlException se) { // code to delay the next loop backoff = true; } catch (Exception e) { } catch (Throwable t) { } } } }
如果你用这种方式来实现你的任务,那么在checkTasks()方法中看不到第三个“看门狗”线程。 此外,出于同样的原因,我上面提到的,我只是谨慎的执行者再次开始任务。 首先,你需要明白为什么任务失败,以及环境是否处于稳定状态,再次运行任务将会有用。
除了目测,我通常运行Java代码对静态分析工具,如PMD和FindBugs寻找更深层次的问题。
特别是对于这个代码,FindBugs不喜欢那个results1,而且在惰性init中结果2不是volatile,并且run()方法可能会忽略这个Exception,因为它们没有被明确地处理。
一般来说,对于使用Thread.sleep进行并发testing,我倾向于使用Thread.sleep ,喜欢定时器或终止状态/条件。 如果无法计算结果,Callable可能会在发生exception时返回exception。
对于一些最佳实践和更多的思考,请查看实践中的并发性 。
你有没有试过Quartz框架 ?
这个怎么样
Runnable task = () -> { try{ // do the task steps here } catch (Exception e){ Thread.sleep (TIME_FOR_LONGER_BREAK); } }; ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); executor.scheduleAtFixedRate(task,0, 0,TimeUnit.SECONDS);