Вилка Java / Join vs ExecutorService-когда использовать что?
Я только что закончил читать этот пост:в чем преимущество Java-5 ThreadPoolExecutor над Java-7 ForkJoinPool? и почувствовал, что ответ недостаточно прямой.
можете ли вы объяснить простым языком и примерами, что такое компромисс между платформой Fork-Join Java 7 и старыми решениями?
Я также прочитал # 1 хит Google по теме Java Tip: когда использовать ForkJoinPool vs ExecutorService от javaworld.com но статья не отвечает на вопрос заголовка , когда, он говорит о различиях api в основном ...
6 ответов
Fork-join позволяет легко выполнять задания divide и conquer, которые должны быть реализованы вручную, если вы хотите выполнить их в ExecutorService. На практике ExecutorService обычно используется для одновременной обработки многих независимых запросов (aka transaction) и fork-join, когда вы хотите ускорить одно согласованное задание.
Fork-join особенно хорош для рекурсивные проблемы, когда задача включает выполнение подзадач, а затем обработку их результатов. (Обычно это называется "разделяй и властвуй" ... но это не раскрывает существенных характеристик.)
Если вы попытаетесь решить рекурсивную проблему, такую как эта, используя обычную потоковую обработку (например, через ExecutorService), вы получите потоки, связанные с ожиданием других потоков для доставки им результатов.
на с другой стороны, если проблема не имеет этих характеристик, нет никакой реальной выгоды от использования fork-join.
Java 8 предоставляет еще один API в исполнителях
static ExecutorService newWorkStealingPool()
создает пул рабочих потоков, используя все доступные процессоры в качестве целевого уровня параллелизма.
С добавлением этого API,исполнители предоставляет различные типы ExecutorService параметры.
в зависимости от вашего требования, вы можете выбрать один из них или вы можете посмотреть на ThreadPoolExecutor что обеспечивает лучший контроль над ограниченным размером очереди задач,RejectedExecutionHandler
механизмы.
-
static ExecutorService newFixedThreadPool(int nThreads)
создает пул потоков, который повторно использует фиксированное количество потоков, работающих с общей неограниченной очередью.
-
static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
создает пул потоков, который может планировать выполнение команд после заданной задержки или выполнение периодически.
-
static ExecutorService newCachedThreadPool(ThreadFactory threadFactory)
создает пул потоков, который создает новые потоки по мере необходимости, но будет повторно использовать ранее построенные потоки, когда они доступны, и использует предоставленный ThreadFactory для создания новых потоков при необходимости.
-
static ExecutorService newWorkStealingPool(int parallelism)
создает пул потоков, который поддерживает достаточно потоков для поддержки заданного уровня параллелизма и может использовать несколько очередей для уменьшения разногласий.
каждый из этих API предназначены для выполнения соответствующих бизнес-потребностей вашего приложения. Какой из них использовать будет зависеть от вашего требования к прецеденту.
например
если вы хотите обработать все представленные задачи в порядке прибытия, просто используйте
newFixedThreadPool(1)
если вы хотите оптимизируйте производительность больших вычислений рекурсивных задач, используйте
ForkJoinPool
илиnewWorkStealingPool
если вы хотите выполнить некоторые задачи периодически или в определенное время в будущем, используйте
newScheduledThreadPool
посмотрите на еще один хороший статьи by PeterLawrey
on ExecutorService
варианты использования.
связанный вопрос SE:
Fork-Join framework-это расширение для executor framework, в частности, для решения проблем "ожидания" в рекурсивных многопоточных программах. Фактически, все новые классы фреймворка Fork-Join распространяются на существующие классы фреймворка Executor.
есть 2 Характеристики Центральной для Fork-Join framework
- Кража работы (простой поток крадет работу из потока, имеющего задачи в очереди больше, чем он может обработать в настоящее время)
- возможность рекурсивного разложения задач и сбора результатов. (По-видимому, это требование должно было появиться вместе с концепция понятия параллельной обработки... но не хватало твердого рамки реализации в Java до Java 7)
Если потребности параллельной обработки строго рекурсивны, нет выбора, кроме как пойти на Fork-Join, в противном случае либо executor, либо Fork-Join framework должны сделать, хотя Fork-Join можно сказать лучше используйте ресурсы из-за простоя потоков "кражи" некоторых задач из более загруженных потоков.
Fork Join-это реализация ExecuterService. Основное отличие заключается в том, что эта реализация создает пул работников DEQUE. Где задача вставляется из oneside, но удаляется с любой стороны. Это означает, что если вы создали new ForkJoinPool()
Он будет искать доступный процессор и создавать столько рабочих потоков. Затем равномерно распределите нагрузку по каждому потоку. Но если один поток работает медленно, а другие быстро, они выберут задачу из медленного потока. сзади. Ниже шаги лучше проиллюстрируют кражу.
Этап 1 (изначально):
W1 - > 5,4,3,2,1
Да2 -> 10,9,8,7,6
Этап 2:
W1 - > 5,4
W2 - > 10,9,8,7,
Этап 3.
W1 - > 10,5,4
W2 - > 9,8,7,
в то время как Служба Executor создает запрашиваемый номер потока и применяет очередь блокировки для хранения всех оставшихся задач ожидания. Если вы использовали cachedExecuterService, он создаст один поток для каждого задания и очереди не будет.
Брайан Гетц описывает лучшей ситуации: https://www.ibm.com/developerworks/library/j-jtp11137/index.html
использование обычных пулов потоков для реализации fork-join также сложно, потому что задачи fork-join проводят большую часть своей жизни в ожидании других задач. Это поведение является рецептом для тупика голода потока, если параметры тщательно не выбраны для ограничения количества созданных задач или сам пул не ограничен. Обычный пулы потоков предназначены для задач, которые не зависят друг от друга и также разработаны с потенциально блокирующими, крупнозернистыми задачами в решениях mind-fork - join не производят ни того, ни другого.
Я рекомендую прочитать весь пост, так как в нем есть хороший пример того, почему вы хотите использовать пул fork-join. Это было написано до того, как ForkJoinPool стал официальным, так что coInvoke()
метод он ссылается на стал invokeAll()
.