В чем преимущество Java-5 ThreadPoolExecutor перед Java-7 ForkJoinPool? - PullRequest
34 голосов
/ 14 февраля 2012

В 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 ]

15 голосов
/ 28 апреля 2012

ThreadPool (TP) и ForkJoinPool (FJ) предназначены для разных вариантов использования. Основное различие заключается в количестве очередей, используемых различными исполнителями, которые решают, какой тип задач лучше подходит для любого исполнителя.

Исполнитель FJ имеет n (он же уровень параллелизма) отдельных параллельных очередей (запросов), в то время как исполнитель TP имеет только одну параллельную очередь (эти очереди / запросы могут быть пользовательскими реализациями, не соответствующими API коллекций JDK). В результате в сценариях, где генерируется большое количество (обычно относительно коротких) задач, исполнитель FJ будет работать лучше, поскольку независимые очереди минимизируют одновременные операции, а нечастые кражи помогут с балансировкой нагрузки. В TP из-за единственной очереди будут выполняться параллельные операции каждый раз, когда работа будет отложена, и это будет действовать как относительное узкое место и ограничивать производительность.

В отличие от этого, если количество выполняемых задач относительно меньше, одна очередь в TP больше не является узким местом для производительности. Однако n-независимые очереди и относительно частые попытки кражи работы теперь станут узким местом в FJ, так как может быть много бесполезных попыток украсть работу, которые увеличивают накладные расходы.

Кроме того, алгоритм кражи работы в FJ предполагает, что (старые) задачи, украденные из очереди, будут производить достаточно параллельных задач, чтобы уменьшить количество краж. Например. в быстрой сортировке или объединенной сортировке, где более старые задачи равняются большим массивам, эти задачи будут генерировать больше задач и сохранять непустую очередь и уменьшать общее количество краж. Если это не так в данном приложении, то частые попытки кражи снова становятся узким местом. Это также отмечено в javadoc для ForkJoinPool :

этот класс предоставляет методы проверки состояния (например, getStealCount ()) которые предназначены для помощи в разработке, настройке и мониторинге приложения fork / join.

11 голосов
/ 14 февраля 2012

Рекомендуемое чтение http://gee.cs.oswego.edu/dl/jsr166/dist/docs/ Из документов для ForkJoinPool:

ForkJoinPool отличается от других видов ExecutorService в основном за счет использования кражи работы: все потоки в пуле пытаютсянаходить и выполнять задачи, переданные в пул и / или созданные другими активными задачами (в конечном итоге блокирующее ожидание работы, если ее не существует).Это обеспечивает эффективную обработку, когда большинство задач порождают другие подзадачи (как и большинство ForkJoinTasks), а также когда много небольших задач передаются в пул от внешних клиентов.Особенно при установке asyncMode в true в конструкторах, ForkJoinPools также может быть подходящим для использования с задачами в стиле событий, которые никогда не объединяются.

Среда объединения вил полезна для параллельного выполнения, в то время как служба executor допускает одновременное выполнениеисполнение и есть разница.См. this и this .

Фреймворк форка соединения также позволяет выполнять кражу работы (использование Deque).

Эта статья - хорошее чтение.

2 голосов
/ 14 февраля 2012

AFAIK, ForkJoinPool работает лучше всего, если у вас большой кусок работы, и вы хотите, чтобы он автоматически разбивался. ThreadPoolExecutor - лучший выбор, если вы знаете, как хотите, чтобы работа была разбита. По этой причине я склонен использовать последний, потому что я определил, как я хочу, чтобы работа была разбита. Как таковой это не для всех.

Ничего не стоит, что когда дело доходит до относительно случайных фрагментов бизнес-логики, ThreadPoolExecutor сделает все, что вам нужно, так зачем делать это сложнее, чем нужно.

1 голос
/ 18 января 2016

Давайте сравним различия в конструкторах:

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 в Исполнители - newWorkStealingPool для создания пула кражи работы. Вам не нужно создавать RecursiveTask и RecursiveAction, но вы все равно можете использовать ForkJoinPool.

public static ExecutorService newWorkStealingPool()

Создает пул потоков для кражи работ, используя все доступные процессоры в качестве целевого уровня параллелизма.

Преимущества ThreadPoolExecutor перед ForkJoinPool:

  1. Вы можете контролировать размер очереди задач в ThreadPoolExecutor в отличие от ForkJoinPool.
  2. Вы можете применять Политику отказа, когда вы исчерпали свои возможности, в отличие от ForkJoinPool

Мне нравятся эти две функции в ThreadPoolExecutor, которые поддерживают работоспособность системы в хорошем состоянии.

EDIT:

Ознакомьтесь с этой статьей для примеров использования различных типов пулов потоков службы Executor Service и оценки возможностей ForkJoin Pool .

...