Как правильно использовать Java Executor?
Я использовал Java Executors в своих многопоточных приложениях, но я не могу понять, когда лучше всего использовать каждый из следующих способов:
1.
ExecutorService executor=Executors.newFixedThreadPool(50);
executor.execute(new A_Runner(... some parameter ...));
executor.shutdown();
while (!executor.isTerminated()) { Thread.sleep(100); }
2.
int Page_Count=200;
ExecutorService executor=Executors.newFixedThreadPool(50);
doneSignal=new CountDownLatch(Page_Count);
for (int i=0;i<Page_Count;i++) executor.execute(new A_Runner(doneSignal, ... some parameter ...));
doneSignal.await();
executor.shutdown();
while (!executor.isTerminated()) { Thread.sleep(100); }
3.
int Executor_Count=30;
ThreadPoolExecutor executor=new ThreadPoolExecutor(Executor_Count,Executor_Count*2,1,TimeUnit.SECONDS,new LinkedBlockingQueue());
List<Future<String>> futures=new ArrayList<>(3330);
for (int i=0;i<50;i++) futures.add(executor.submit(new A_Runner(... some parameter ...));
executor.shutdown();
while (!executor.isTerminated()) { executor.awaitTermination(1,TimeUnit.SECONDS); }
for (Future<String> future : futures)
{
String f=future.get();
// ...
}
В частности, в [2], что если я пропущу doneSignal, то это будет похоже на [1], так что толку от DoneSignal?
Кроме того, в [3], что если я добавлю doneSignal? Или это возможно?
Что я хотел бы знать: взаимозаменяемы ли эти подходы, или есть определенная ситуация, в которой я должен использовать определенный тип выше?
1 ответ
ExecutorService executor=Executors.newFixedThreadPool(50);
Это просто и удобно в использовании. Скрывает детали низкого уровня
ThreadPoolExecutor
,Предпочитаю этот, когда число
Callable/Runnable
задач не много, а накопление задач в неограниченной очереди не увеличивает объем памяти и не снижает производительность системы. Если у вас естьCPU/Memory
ограничения, использованиеThreadPoolExecutor
с ограничениями по мощностиRejectedExecutionHandler
обрабатывать отказ от задач.Вы инициализировали
CountDownLatch
с заданным количеством. Этот счет уменьшается при обращении кcountDown()
метод. Я предполагаю, что вы вызываете декремент в своей задаче Runnable позже. Потоки, ожидающие, пока это число достигнет нуля, могут вызвать один изawait()
методы. призваниеawait()
блокирует поток, пока счетчик не достигнет нуля. Этот класс позволяет потоку Java ждать, пока другой набор потоков не выполнит свои задачи.Случаи применения:
Достижение максимального параллелизма: иногда мы хотим запустить несколько потоков одновременно для достижения максимального параллелизма
Подождите N потоков до завершения выполнения
Обнаружение тупика.
Взгляните на эту статью Локеша Гупты для более подробной информации.
ThreadPoolExecutor: предоставляет больше возможностей для настройки различных параметров пула потоков. Если ваша заявка ограничена количеством активных
Runnable/Callable
задач, вы должны использовать ограниченную очередь, установив максимальную емкость. Как только очередь достигает максимальной емкости, вы можете определить RejectionHandler. Java предоставляет четыре типаRejectedExecutionHandler
политики.По умолчанию
ThreadPoolExecutor.AbortPolicy
обработчик генерирует исключительную ситуацию RejectedExecutionException при отклонении.В
ThreadPoolExecutor.CallerRunsPolicy
, поток, который вызывает execute, сам выполняет задачу. Это обеспечивает простой механизм управления с обратной связью, который замедляет скорость отправки новых задач.В
ThreadPoolExecutor.DiscardPolicy
задача, которая не может быть выполнена, просто отбрасывается.В
ThreadPoolExecutor.DiscardOldestPolicy
, если исполнитель не выключен, задача во главе рабочей очереди отбрасывается, а затем повторяется попытка выполнения (что может снова привести к сбою, в результате чего это повторяется).Если вы хотите смоделировать
CountDownLatch
поведение, вы можете использоватьinvokeAll()
метод.
Еще один механизм, который вы не цитировали - это ForkJoinPool
ForkJoinPool
был добавлен в Java в Java 7.ForkJoinPool
похож на JavaExecutorService
но с одним отличием.ForkJoinPool
позволяет задачам разделить свою работу на более мелкие задачи, которые затем передаютсяForkJoinPool
тоже. Кража задания происходит вForkJoinPool
когда свободные рабочие потоки крадут задачи из очереди занятых рабочих потоков.Java 8 представила еще один API в ExecutorService для создания пула кражи работы. Вам не нужно создавать
RecursiveTask
а такжеRecursiveAction
но все еще можно использоватьForkJoinPool
,public static ExecutorService newWorkStealingPool()
Создает пул потоков для кражи работ, используя все доступные процессоры в качестве целевого уровня параллелизма.
По умолчанию в качестве параметра будет использовано количество ядер ЦП.
Все эти четыре механизма дополняют друг друга. В зависимости от уровня детализации, который вы хотите контролировать, вы должны выбрать правильные.