36

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

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

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

现在,应该注意的是,这两个任务的实现都会将代码包装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

4 回答 4

31

我在之前的项目中遇到过类似的情况,在我的代码在面对愤怒的客户时崩溃后,我和我的伙伴们添加了两个大保障:

  1. 在无限循环中,也要捕获错误,而不仅仅是异常。有时会发生不寻常的事情,Java 会向您抛出错误,而不是异常。
  2. 使用回退开关,因此如果出现问题并且不可恢复,您不会急切地启动另一个循环来升级情况。相反,您需要等到情况恢复正常,然后重新开始。

例如,我们遇到了一种情况,即数据库出现故障,并且在循环期间抛出了 SQLException。不幸的结果是代码再次循环,只是再次遇到相同的异常,依此类推。日志显示我们在一秒钟内遇到了大约 300 次相同的 SQLException!...这间歇性地发生了好几次,偶尔 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() 方法拥有第三个“看门狗”线程是没有意义的。此外,出于我上面概述的相同原因,我会谨慎地与执行者再次开始任务。首先,您需要了解任务失败的原因以及环境是否处于稳定状态,再次运行任务会有用。

于 2010-01-20T21:35:55.470 回答
7

除了目测之外,我通常针对PMDFindBugs等静态分析工具运行 Java 代码来寻找更深层次的问题。

特别是对于这段代码,FindBugs 不喜欢 results1 和 results2 在惰性初始化中不是易失的,并且 run() 方法可能会忽略异常,因为它们没有被显式处理。

一般来说,我对使用Thread.sleep进行并发测试、更喜欢计时器或终止状态/条件持怀疑态度。如果无法计算结果而引发异常的中断事件,Callable 可能有助于返回某些内容。

有关一些最佳实践和更多思考的内容,请查看Concurrency in Practice

于 2010-01-20T21:20:58.500 回答
1

这个怎么样

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);
于 2017-02-06T18:02:49.203 回答
0

你试过Quartz 框架吗?

于 2010-01-21T13:16:19.930 回答