В одном из наших сервисов кто-то добавил такой (упрощенный) фрагмент кода:
public class DeleteMe {
public static void main(String[] args) {
DeleteMe d = new DeleteMe();
for (int i = 0; i < 10_000; ++i) {
d.trigger(i);
}
}
private Future<?> trigger(int i) {
ExecutorService es = Executors.newSingleThreadExecutor();
Future<?> f = es.submit(() -> {
try {
// some long running task
Thread.sleep(10_000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
return f;
}
}
Это не удается иногда с:
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@3148f668 rejected from java.util.concurrent.ThreadPoolExecutor@6e005dc9[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:112)
at java.util.concurrent.Executors$DelegatedExecutorService.submit(Executors.java:678)
at com.erabii.so.DeleteMe.trigger(DeleteMe.java:29)
at com.erabii.so.DeleteMe.main(DeleteMe.java:22)
Большинствовремени ошибка OutOfMemoryError
- что я прекрасно понимаю. Человек, пишущий код, никогда не вызывал ExecutorService::shutDown
, таким образом поддерживая его слишком много. Конечно, создание отдельной службы-исполнителя для каждого вызова метода является плохим и будет изменено;но именно поэтому ошибка видна.
Суть, которую я не понимаю, заключается в том, почему RejectedExecutionException
будет выброшено, в частности, оно выбрасывается здесь .
Кодовые комментарии там иметь некоторый смысл:
Если мы не можем поставить задачу в очередь, то мы пытаемся добавить новый поток. Если это не удается, мы знаем, что мы выключены или насыщены и поэтому отклоняем задачу.
Если это действительно так, то как получается документация * 1029? * не упоминает об этом?
Если задача не может быть отправлена на выполнение, либо из-за того, что этот исполнитель был закрыт, либо из-за того, что его емкость достигнута, задача обрабатывается текущим RejectedExecutionHandler.
Честно говоря, изначально я думал, что ExecutorService
- это GC-ed - достижимость и область видимости - разные вещи, и GC разрешено очищать все, что не достижимо;но есть Future<?>
, который будет строго ссылаться на эту службу, поэтому я исключил это.