В чем преимущество Java-5 ThreadPoolExecutor над Java-7 ForkJoinPool?

Java 5 представила поддержку асинхронного выполнения задачи пулом потоков в виде платформы Executor, сердцем которого является пул потоков, реализованный java.утиль.параллельный.ThreadPoolExecutor. Java 7 добавила альтернативный пул потоков в виде java.утиль.параллельный.ForkJoinPool.

глядя на их соответствующий API, ForkJoinPool предоставляет надмножество функций ThreadPoolExecutor в стандартных сценариях (хотя, строго говоря, ThreadPoolExecutor предлагает больше возможностей для настройки, чем ForkJoinPool). Добавим к этому замечание, что задачи fork / join кажутся быстрее (возможно, из-за планировщика кражи работы), нужно определенно меньше потоков (из-за неблокирующей операции соединения), может сложиться впечатление, что ThreadPoolExecutor был заменен ForkJoinPool.

но это действительно правильно? Весь материал, который я прочитал, кажется, подводит итог довольно расплывчатому различию между двумя типами пулов потоков:

  • ForkJoinPool для многих, зависимых, сгенерированных задач, коротких, почти никогда не блокирующих (т. е. вычислительных) задач
  • ThreadPoolExecutor предназначен для немногих, независимых, генерируемых извне, длинных, иногда блокирующих задач

Это различие корректным? Можем ли мы сказать что-нибудь более конкретное об этом?

4 ответов


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

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

напротив, если относительно меньше долгосрочных задач, то одной очереди в ТП больше не является узким местом для производительности. Однако N-независимые очереди и относительно частые попытки кражи работы теперь станут узким местом в FJ, поскольку может быть много бесполезных попыток украсть работу, которые добавляют к накладным расходам.

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

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


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

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

платформа Fork join полезна для параллельного выполнения, в то время как служба executor допускает параллельное выполнение, и есть разница. См.этой и этой.

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

public static ExecutorService newWorkStealingPool()

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

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

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

мне нравятся эти две функции в ThreadPoolExecutor, который сохраняет здоровье системы в хорошем состоянии.

EDIT:

посмотри статьи для случаев использования различных типов пулов потоков службы исполнителя и оценки ForkJoin Пул функции.