Какое преимущество Java-5 ThreadPoolExecutor над Java-7 ForkJoinPool?
Java 5 представила поддержку для выполнения асинхронной задачи пулом потоков в виде инфраструктуры Executor, чье сердце является пулом потоков, реализованным java.util.concurrent.ThreadPoolExecutor. Java 7 добавила альтернативный пул потоков в виде java.util.concurrent.ForkJoinPool.
Посмотрев на свой API, ForkJoinPool обеспечивает надстройку функциональности ThreadPoolExecutor в стандартных сценариях (хотя, строго говоря, ThreadPoolExecutor предлагает больше возможностей для настройки, чем ForkJoinPool). Добавив к этому замечание, что
fork/join задачи кажутся более быстрыми (возможно, из-за планировщика кражи работы), нужно определенно меньше потоков (из-за операции неблокирующего соединения), может возникнуть впечатление, что ThreadPoolExecutor был заменен ForkJoinPool.
Но это действительно правильно? Весь материал, который я прочитал, похоже, сводится к довольно расплывчатому различию между двумя типами пулов потоков:
- ForkJoinPool предназначен для многих, зависимых, заданных, коротких, вряд ли когда-либо блокирующих (то есть вычислительных) задач
- ThreadPoolExecutor - это несколько, независимых, созданных извне, длинных, иногда блокирующих задач.
Является ли это различие правильным? Можем ли мы сказать что-то более конкретное об этом?
Ответы
Ответ 1
ThreadPool (TP) и ForkJoinPool (FJ) ориентированы на различные варианты использования. Основное различие заключается в количестве очередей, используемых различными исполнителями, которые решают, какие проблемы лучше подходят для любого исполнителя.
Исполнитель FJ имеет n (aka parallelism level) отдельные параллельные очереди (deques), в то время как исполнитель TP имеет только одну параллельную очередь (эти очереди /deques, возможно, пользовательские реализации, не соответствующие API-интерфейсам JDK Collections). В результате в сценариях, где вы создаете большое количество (обычно относительно коротких) задач, исполнитель FJ будет работать лучше, поскольку независимые очереди минимизируют параллельные операции, а нечастые перехваты помогут с балансировкой нагрузки. В TP из-за одиночной очереди будут выполняться параллельные операции каждый раз, когда работа будет удалена, и она будет действовать как относительное узкое место и ограничить производительность.
В отличие от этого, если относительно меньше длительных задач одиночная очередь в TP больше не является узким местом для производительности. Тем не менее, n-независимые очереди и относительно частые попытки кражи работы теперь станут узким местом в FJ, так как может быть много тщетных попыток украсть работу, которая добавит накладные расходы.
Кроме того, алгоритм поиска работы в FJ предполагает, что (более старые) задачи, украденные из deque, будут давать достаточно параллельных задач для уменьшения количества краж. Например. в quicksort или mergesort, где более старые задачи равны более крупным массивам, эти задачи будут генерировать больше задач и держать очередь непустой и уменьшать количество общих краж. Если это не так в данном приложении, то частые попытки кражи снова становятся узким местом. Это также отмечено в javadoc для ForkJoinPool:
этот класс предоставляет методы проверки состояния (например, getStealCount()) которые призваны помочь в разработке, настройке и мониторинге fork/join.
Ответ 2
Рекомендуемое чтение http://gee.cs.oswego.edu/dl/jsr166/dist/docs/
Из документов для ForkJoinPool:
A ForkJoinPool отличается от других видов ExecutorService главным образом в силу использования кражи работы: все потоки в пуле пытаются находить и выполнять задачи, представленные в пул и/или созданные другими активные задачи (в конечном счете, блокирование ожидания работы, если они не существуют). Это позволяет эффективно обрабатывать, когда большинство задач порождают другие подзадачи (как и большинство ForkJoinTasks), а также когда многие небольшие задачи представленный в пул от внешних клиентов. Особенно при настройке asyncMode - true в конструкторах, ForkJoinPools также может быть подходящий для использования с задачами в стиле событий, которые никогда не соединяются.
Структура fork join полезна для параллельного выполнения, в то время как служба-исполнитель допускает параллельное выполнение, и есть разница. См. this и this.
Структура fork join также позволяет кражу работы (использование Deque).
Эта статья хорошо читается.
Ответ 3
AFAIK, ForkJoinPool
работает лучше всего, если вы выполняете большую работу и хотите, чтобы она автоматически разбилась. ThreadPoolExecutor
- лучший выбор, если вы знаете, как вы хотите, чтобы работа была разрушена. По этой причине я склонен использовать последнее, потому что я определил, как я хочу, чтобы работа была разрушена. Таким образом, это не для каждого.
Не стоит ничего, что когда дело доходит до относительно случайных частей бизнес-логики, ThreadPoolExecutor сделает все, что вам нужно, поэтому зачем это усложнять, чем вам нужно.
Ответ 4
Сравним различия в конструкторах:
ThreadPoolExecutor
ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
ForkJoinPool
ForkJoinPool(int parallelism,
ForkJoinPool.ForkJoinWorkerThreadFactory factory,
Thread.UncaughtExceptionHandler handler,
boolean asyncMode)
Единственное преимущество, которое я видел в ForkJoinPool
: механизм кражи работы с помощью незанятых потоков.
Java 8 представила еще один API в Executors - newWorkStealingPool, чтобы создать пул кражи работы. Вам не нужно создавать RecursiveTask
и RecursiveAction
, но все же можете использовать ForkJoinPool
.
public static ExecutorService newWorkStealingPool()
Создает пул потоков обработки, используя все доступные процессоры в качестве целевого уровня parallelism.
Преимущества ThreadPoolExecutor для ForkJoinPool:
- Вы можете контролировать размер очереди задач в
ThreadPoolExecutor
в отличие от ForkJoinPool
.
- Вы можете применить политику отклонения, когда у вас закончилась ваша емкость, в отличие от
ForkJoinPool
Мне нравятся эти две функции в ThreadPoolExecutor
, которые сохраняют работоспособность системы в хорошем состоянии.
EDIT:
Посмотрите на эту статью для использования примеров различных типов пулов потоков служб Executor и оценки Возможности ForkJoin Pool.