Рекомендации Java Executor для задач, которые должны выполняться навсегда
Я работаю над проектом Java, где мне нужно, чтобы несколько задач выполнялись асинхронно. Я убежден, что Исполнитель - лучший способ для меня сделать это, поэтому я знакомлюсь с ним. (Я получаю деньги, чтобы учиться!) Однако мне непонятно, какой лучший способ - выполнить то, что я пытаюсь сделать.
Для аргумента предположим, что у меня две задачи. Ожидается, что ни один из них не завершится, и оба должны работать в течение срока действия приложения. Я пытаюсь написать основной класс-оболочку, чтобы:
- Если какая-либо из задач выдает исключение, оболочка поймает ее и перезапустит задачу.
- Если любая задача выполняется до завершения, оболочка заметит и перезапустит задачу.
Теперь следует отметить, что реализация для обеих задач будет завершать код в 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) {}
}
}
}
Он ведет себя так, как я хочу, но я не знаю, есть ли какие-либо ошибки, неэффективность или просто неправильная голова, ожидающая удивить меня. (На самом деле, учитывая, что я новичок в этом, я был бы потрясен, если бы не было что-то не так/нецелесообразно.)
Любое понимание приветствуется.
Ответы
Ответ 1
Я столкнулся с аналогичной ситуацией в своем предыдущем проекте, и после того, как мой код взорвался перед лицом сердитого клиента, мои приятели и я добавили двух больших охранников:
- В бесконечном цикле поймать Ошибки тоже, а не только исключения. Иногда происходят непредвиденные события, и Java выдает вам ошибку, а не исключение.
- Используйте резервный переключатель, поэтому, если что-то пойдет не так и не восстанавливается, вы не эскалируете ситуацию, нетерпеливо запуская другой цикл. Вместо этого вам нужно подождать, пока ситуация вернется к нормальному состоянию, а затем снова начнется.
Например, у нас была ситуация, когда база данных опустилась, и во время цикла было выбрано 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(). Кроме того, по тем же причинам, о которых я говорил выше, я был бы осторожен, чтобы снова начать задание с исполнителем. Сначала вам нужно понять, почему задача не удалась, и будет ли среда в стабильном состоянии снова запускать задачу.
Ответ 2
Кроме того, я обычно запускаю Java-код против инструментов статического анализа, таких как PMD и FindBugs, чтобы искать более глубокие проблемы.
В частности, для этого кода FindBugs не понравилось, что результаты1 и результаты2 не являются изменчивыми в ленивом init, и что методы run() могут игнорировать исключение, потому что они явно не обрабатываются.
В общем, я немного склонен к использованию Thread.sleep для тестирования concurrency, предпочитая таймеры или прекращающие состояния/условия, Вызываемый может быть полезен при возвращении чего-либо в случае сбоя, который генерирует исключение, если не удается вычислить результат.
Для некоторых лучших практик и большего количества пищи для размышлений проверьте Concurrency на практике.
Ответ 3
Вы пробовали Кварцевые рамки?
Ответ 4
как насчет этого
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);