Рекомендации Java Executor для задач, которые должны выполняться вечно - PullRequest
36 голосов
/ 20 января 2010

Я работаю над проектом 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 ]

31 голосов
/ 21 января 2010

Я столкнулся с подобной ситуацией в моем предыдущем проекте, и после того, как мой код взорвался перед лицом разгневанного клиента, мои друзья и я добавили два больших средства защиты:

  1. В бесконечном цикле перехватывайте и ошибки, а не только исключения. Иногда происходят непредвиденные вещи, и Java выдает ошибку, а не исключение.
  2. Используйте переключатель отключения, поэтому, если что-то пойдет не так и не подлежит восстановлению, вы не обострите ситуацию, нетерпеливо запустив еще один цикл. Вместо этого вам нужно подождать, пока ситуация не вернется к нормальной жизни, а затем начать снова.

Например, у нас была ситуация, когда база данных отключалась, и во время цикла возникало исключение 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 (). Кроме того, по тем же причинам, которые я изложил выше, я бы с осторожностью просто снова запустил задачу с исполнителем. Прежде всего вам необходимо понять, почему задача не выполнена, и находится ли среда в стабильном состоянии, что повторное выполнение задачи было бы полезно.

7 голосов
/ 21 января 2010

Помимо этого, я обычно запускаю Java-код для инструментов статического анализа, таких как PMD и FindBugs , для поиска более глубоких проблем.

Специально для этого кода FindBugs не понравилось, что results1 и results2 не являются изменчивыми в ленивом init, и что методы run () могут игнорировать исключение, поскольку они не обрабатываются явно.

В общем, я немного опасаюсь использования Thread.sleep для тестирования параллелизма, предпочитая таймеры или завершающие состояния / условия. Функция Callable может быть полезна для возврата чего-либо в случае сбоя, который выдает исключение, если не удается вычислить результат.

Чтобы ознакомиться с лучшими практиками и пищей для размышлений, ознакомьтесь с Параллелизмом на практике .

0 голосов
/ 06 февраля 2017

как насчет этого

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);
0 голосов
/ 21 января 2010

Вы пробовали Кварцевые рамки ?

...