Странное поведение ExecutorService - PullRequest
0 голосов
/ 03 ноября 2011

У меня есть 5000 похожих вызываемых задач, которые должны быть выполнены в 8 потоках ExecutorService, созданных Executors.newFixedThreadPool (8).Каждая задача отправляется в базу данных для извлечения большого количества данных для обработки.

Все отлично работает 99% времени, НО иногда я вижу очень странные сообщения журнала выполнения в файле журнала, когда БД работает медленно или зависает (не спрашивайте, почему), и 8 выполняющихся в данный момент задач останавливаются и не работаютЗакончив еще во всех 8 потоках, ExecutorService начинает посылать больше задач для выполнения одну за другой!

Так что журнал показывает, что в какой-то момент ExecutorService сходит с ума и начинает вызывать метод call () Callable для все большего числа задач вочередь ожидания, не ожидая завершения предыдущих задач.Все больше и больше задач отправляют запросы в БД, что в итоге ставит БД на колени, и куча памяти Java исчерпывается.

Похоже, что-то странное происходит внутри ExecutorService, или мое понимание ситуации неверно.Кто-нибудь видел что-нибудь подобное?

Мой мозговой стек переполнен

ps - цитата из API Java:

Executors.newFixedThreadPool (int nThreads)

Создаетпул потоков, который повторно использует фиксированное число потоков, работающих в общей неограниченной очереди.В любой момент не более nThreads потоков будут активными задачами обработки.Если дополнительные задачи отправляются, когда все потоки активны, они будут ждать в очереди, пока поток не станет доступным. Если какой-либо поток завершается из-за сбоя во время выполнения перед завершением работы, при необходимости для выполнения последующих задач его место занимает новый. .

Может ли это произойти на самом деле, если мои задачизаставить поток умирать, и ExecutorService создает больше потоков и отправляет им новые 8 задач, и они умирают, а ExecutorService создает еще 8 потоков и отправляет больше 8 задач?

pss: вся операция внутри call () Callable окруженас try catch, так что если в моей работе происходит какое-либо исключение, то оно будет записано и зарегистрировано.Ничего этого не происходит.Вызов вызывается и просто никогда не возвращается, в то время как следующие задачи выполняются по очереди, одна за другой, никогда не возвращаются и никогда не завершаются и никогда не выдают никаких исключений.

Я подозреваю, что мои задачи приводят к смерти потоков в пуле потоков.Как можно подражать?

Ответы [ 2 ]

3 голосов
/ 03 ноября 2011

Я тоже попробую угадать:

  1. Вы отправляете 5000 задач, которые включают выборку данных из базы данных.
  2. Вскоре после этого вы сталкиваетесь с сильным конфликтом блокировок на требуемых строках / таблицах. Возможно, внешние процессы приобретают эксклюзивные блокировки для записи. Может быть, есть тупик.
  3. Один за другим блокируются задачи, ожидающие предоставления блокировки совместного использования / чтения.
  4. Похоже, что все 8 потоков приостановлены, ожидая I/O.
  5. Вскоре после этого драйвер базы данных / БД замечает, что задачи слишком долго ждали совместной блокировки. В итоге он раздает Lock Wait Timeout исключений для задач по порядку.
  6. Таким образом, одна за другой задачи выходят из строя из очереди, и ожидающие задачи помещаются в исполнение, только для повторного сбоя.

Обратите внимание, что исключение в задании не остановит ExecutorService. Это просто пометит эту задачу как выполненную и продолжит.

См. Этот пример:

public class Foo {

    static class Task implements Callable<String> {
        private static AtomicInteger i = new AtomicInteger(1);

        public String call() throws Exception {
            i.incrementAndGet();
            if (i.get() % 2 != 0) {
                throw new RuntimeException("That's odd, I failed.");
            }
            return "I'm done";
        }
    }

    public static void main(String[] args) throws Exception {
        ExecutorService es = Executors.newFixedThreadPool(2);
        List<Future<String>> futures = new ArrayList<Future<String>>();
        for (int i = 0; i < 5; i++) {
            futures.add(es.submit(new Task()));
        }
        for (Future<String> future : futures) {
            try {
                System.out.println(future.get());
            } catch (ExecutionException ee) {
                System.err.println(ee.getCause());
            }
        }
        es.shutdown();
    }
}

Возможный вывод:

I'm done
I'm done
I'm done
java.lang.RuntimeException: That's odd, I failed.
java.lang.RuntimeException: That's odd, I failed.
0 голосов
/ 03 ноября 2011

Это всего лишь предположение (я думаю, что предположение заслуживает внимания, учитывая отсутствие кода в вопросе):

ExecutorService.invokeAll(Collection<? extends Callable<T>> tasks) перейдет к другим задачам, если текущие задачи выдают исключение.(вы используете invokeAll ()? Я думаю, что submit(Callable<T> task) имеет такое же поведение, но это не ясно из javadoc)

Можете ли вы проверить, что эти «застрявшие» задачи становятся Future.isDone() до того, как последующиезадачи запускаются?Потенциально исключения генерируются и не отображаются в журналах ...

Из javadoc:

Обратите внимание, что завершенная задача могла быть завершена либо нормально, либо с помощью исключения.

http://download.oracle.com/javase/6/docs/api/java/util/concurrent/ExecutorService.html#invokeAll(java.util.Collection%29

Если это является случаем, вы можете просто перехватить и зарегистрировать все исключения внутри определения Callable.call() метода.

НТН

...