Java Executor应该永远运行的任务的最佳实践

时间:2010-01-20 20:25:04

标签: java executor

我正在开发一个Java项目,我需要异步运行多个任务。我被引导相信Executor是我做这件事的最佳方式,所以我很熟悉它。 (Yay得到报酬学习!)然而,我不清楚最好的方法是完成我想要做的事情。

为了论证,让我说我有两个任务在运行。预计两者都不会终止,并且两者都应该在应用程序的生命周期内运行。我正在尝试编写一个主包装类,以便:

  • 如果任一任务抛出异常,则包装器将捕获它并重新启动任务。
  • 如果任一任务运行完成,则包装器将注意并重新启动任务。

现在,应该注意的是,两个任务的实现都将run()中的代码包装在一个永远不会运行完成的无限循环中,并且try / catch块应该处理所有运行时异常而不会中断循环。我正试图增加另一层确定性;如果我或跟随我的人做了一些愚蠢的事情来挫败这些保护措施并停止任务,那么应用程序需要做出适当的反应。

是否有最佳实践来解决这个问题,那些比我更有经验的人会推荐?

FWIW,我已经掀起了这个测试课:


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) {}
      }
   }
}

它表现得像我想要的那样,但我不知道是否有任何陷阱,效率低下,或者彻头彻尾的错误头脑等着让我感到惊讶。 (事实上​​,鉴于我是新手,如果不是错误/不可取的话,我会感到震惊。)

欢迎任何见解。

4 个答案:

答案 0 :(得分:31)

我在之前的项目中遇到了类似的情况,在我的代码面对愤怒的客户后,我的朋友和我添加了两个大保安:

  1. 在无限循环中,捕获错误,而不仅仅是异常。有时会发生未经发现的事情,Java会向您抛出错误,而不是异常。
  2. 使用后退开关,因此如果出现问题并且无法恢复,则不要通过急切启动另一个循环来升级情况。相反,你需要等到情况恢复正常然后重新开始。
  3. 例如,我们遇到了数据库发生故障的情况,并且在循环期间抛出了SQLException。不幸的结果是代码再次通过循环,只是再次遇到同样的异常,等等。日志显示我们在一秒内达到相同的SQLException大约300次!! ...这种情况间歇性地发生了几次,偶尔的JVM暂停时间为5秒左右,在此期间应用程序没有响应,直到最终抛出错误并且线程死了!

    因此,我们实施了一个退避策略,大致在下面的代码中显示,如果异常无法恢复(或在几分钟内恢复除外),那么我们会在恢复操作之前等待更长的时间。

    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()方法获得第三个“看门狗”线程。此外,出于我在上面概述的相同原因,我会谨慎地再次与执行者一起开始任务。首先,您需要了解任务失败的原因以及环境是否处于稳定状态,再次运行任务会很有用。

答案 1 :(得分:7)

除了关注它之外,我通常会针对静态分析工具(如PMDFindBugs)运行Java代码来寻找更深层次的问题。

特别是对于这段代码,FindBugs不喜欢result1和results2在惰性init中不易变,并且run()方法可能会忽略Exception,因为它们没有被显式处理。

总的来说,我对使用Thread.sleep进行并发测试,偏好定时器或终止状态/条件有点怀疑。如果在无法计算结果的情况下抛出异常,则Callable可能在返回某些内容时非常有用。

对于一些最佳做法和更多思考的食物,请查看Concurrency in Practice

答案 2 :(得分:0)

你试过Quartz framework吗?

答案 3 :(得分:0)

这个怎么样

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