В чем преимущество 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 предназначен для нескольких независимых, сгенерированных извне, долгих, иногда блокирующих задач
Правильно ли это различие? Можем ли мы сказать что-то более конкретное по этому поводу?
4 ответа
ThreadPool (TP) и ForkJoinPool (FJ) предназначены для различных вариантов использования. Основное различие заключается в количестве очередей, используемых разными исполнителями, которые решают, какой тип задач лучше подходит для любого исполнителя.
Исполнитель FJ имеет n (иначе говоря, уровень параллелизма) отдельных параллельных очередей (запросов), в то время как исполнитель TP имеет только одну параллельную очередь (эти очереди / запросы могут быть пользовательскими реализациями, не соответствующими API коллекций JDK). В результате в сценариях, где генерируется большое количество (обычно относительно коротких) задач, исполнитель FJ будет работать лучше, поскольку независимые очереди минимизируют параллельные операции, а нечастые кражи помогут с балансировкой нагрузки. В TP из-за единой очереди будут происходить параллельные операции каждый раз, когда работа будет отложена, и это будет действовать как относительное узкое место и ограничивать производительность.
В отличие от этого, если количество выполняемых задач относительно меньше, одна очередь в TP больше не является узким местом для производительности. Однако n-независимые очереди и относительно частые попытки кражи работы теперь станут узким местом в FJ, так как может быть много бесполезных попыток украсть работу, которые увеличивают накладные расходы.
Кроме того, алгоритм кражи работы в FJ предполагает, что (старые) задачи, украденные из очереди, будут производить достаточно параллельных задач, чтобы уменьшить количество краж. Например, в быстрой сортировке или сортировке слиянием, где более старые задачи равняются большим массивам, эти задачи будут генерировать больше задач и сохранять непустую очередь и уменьшать общее количество краж. Если это не так в данном приложении, то частые попытки кражи снова становятся узким местом. Это также отмечено в javadoc для ForkJoinPool:
этот класс предоставляет методы проверки состояния (например, getStealCount()), предназначенные для помощи в разработке, настройке и мониторинге приложений fork/join.
Рекомендуемое чтение http://gee.cs.oswego.edu/dl/jsr166/dist/docs/ Из документации для ForkJoinPool:
ForkJoinPool отличается от других видов ExecutorService в основном тем, что использует похищение работы: все потоки в пуле пытаются найти и выполнить задачи, отправленные в пул и / или созданные другими активными задачами (в конечном итоге блокируя ожидание работы, если ее не существует), Это обеспечивает эффективную обработку, когда большинство задач порождают другие подзадачи (как и большинство ForkJoinTasks), а также когда много небольших задач передаются в пул от внешних клиентов. Особенно при установке asyncMode в true в конструкторах, ForkJoinPools также может быть подходящим для использования с задачами в стиле событий, которые никогда не объединяются.
Платформа fork fork полезна для параллельного выполнения, в то время как служба executor допускает параллельное выполнение, и есть разница. Смотрите это и это.
Фреймворк fork fork также позволяет выполнять кражу работы (использование Deque).
Эта статья хорошо читается.
НАСКОЛЬКО МНЕ ИЗВЕСТНО, ForkJoinPool
лучше всего работает, если у вас большой кусок работы, и вы хотите, чтобы он разбился автоматически. ThreadPoolExecutor
это лучший выбор, если вы знаете, как вы хотите, чтобы работа была разбита. По этой причине я склонен использовать последнее, потому что я определил, как я хочу, чтобы работа была разбита. Как таковой его не для всех.
Ничего не стоит, что когда дело доходит до относительно случайных фрагментов бизнес-логики, ThreadPoolExecutor сделает все, что вам нужно, так зачем делать это сложнее, чем нужно.
Давайте сравним различия в конструкторах:
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()
Создает пул потоков для кражи работ, используя все доступные процессоры в качестве целевого уровня параллелизма.
Преимущества ThreadPoolExecutor перед ForkJoinPool:
- Вы можете контролировать размер очереди задач в
ThreadPoolExecutor
в отличие отForkJoinPool
, - Вы можете применить Политику Отказа, когда вы исчерпали свои возможности в отличие от
ForkJoinPool
Мне нравятся эти две особенности в ThreadPoolExecutor
который сохраняет здоровье системы в хорошем состоянии.
РЕДАКТИРОВАТЬ:
Ознакомьтесь с этой статьей для примеров использования различных типов пулов потоков службы Executor Service и оценки возможностей пула ForkJoin.