Как правильно использовать Java Executor?

Я использовал Java Executors в своих многопоточных приложениях, но я не могу понять, когда лучше всего использовать каждый из следующих способов:

1.

ExecutorService executor=Executors.newFixedThreadPool(50);
executor.execute(new A_Runner(... some parameter ...));
executor.shutdown();
while (!executor.isTerminated()) { Thread.sleep(100); }

2.

int Page_Count=200;
ExecutorService executor=Executors.newFixedThreadPool(50);
doneSignal=new CountDownLatch(Page_Count);
for (int i=0;i<Page_Count;i++) executor.execute(new A_Runner(doneSignal, ... some parameter ...));
doneSignal.await();
executor.shutdown();
while (!executor.isTerminated()) { Thread.sleep(100); }

3.

int Executor_Count=30;
ThreadPoolExecutor executor=new ThreadPoolExecutor(Executor_Count,Executor_Count*2,1,TimeUnit.SECONDS,new LinkedBlockingQueue());
List<Future<String>> futures=new ArrayList<>(3330);

for (int i=0;i<50;i++) futures.add(executor.submit(new A_Runner(... some parameter ...));
executor.shutdown();
while (!executor.isTerminated()) { executor.awaitTermination(1,TimeUnit.SECONDS); }
for (Future<String> future : futures)
{
    String f=future.get();
    // ...
}

В частности, в [2], что если я пропущу doneSignal, то это будет похоже на [1], так что толку от DoneSignal?

Кроме того, в [3], что если я добавлю doneSignal? Или это возможно?

Что я хотел бы знать: взаимозаменяемы ли эти подходы, или есть определенная ситуация, в которой я должен использовать определенный тип выше?

1 ответ

Решение
  1. ExecutorService

    ExecutorService executor=Executors.newFixedThreadPool(50);

    Это просто и удобно в использовании. Скрывает детали низкого уровня ThreadPoolExecutor,

    Предпочитаю этот, когда число Callable/Runnable задач не много, а накопление задач в неограниченной очереди не увеличивает объем памяти и не снижает производительность системы. Если у вас есть CPU/Memory ограничения, использование ThreadPoolExecutor с ограничениями по мощности RejectedExecutionHandler обрабатывать отказ от задач.

  2. CountDownLatch

    Вы инициализировали CountDownLatch с заданным количеством. Этот счет уменьшается при обращении к countDown() метод. Я предполагаю, что вы вызываете декремент в своей задаче Runnable позже. Потоки, ожидающие, пока это число достигнет нуля, могут вызвать один из await() методы. призвание await() блокирует поток, пока счетчик не достигнет нуля. Этот класс позволяет потоку Java ждать, пока другой набор потоков не выполнит свои задачи.

    Случаи применения:

    1. Достижение максимального параллелизма: иногда мы хотим запустить несколько потоков одновременно для достижения максимального параллелизма

    2. Подождите N потоков до завершения выполнения

    3. Обнаружение тупика.

      Взгляните на эту статью Локеша Гупты для более подробной информации.

  3. ThreadPoolExecutor: предоставляет больше возможностей для настройки различных параметров пула потоков. Если ваша заявка ограничена количеством активных Runnable/Callable задач, вы должны использовать ограниченную очередь, установив максимальную емкость. Как только очередь достигает максимальной емкости, вы можете определить RejectionHandler. Java предоставляет четыре типа RejectedExecutionHandler политики.

    1. По умолчанию ThreadPoolExecutor.AbortPolicyобработчик генерирует исключительную ситуацию RejectedExecutionException при отклонении.

    2. В ThreadPoolExecutor.CallerRunsPolicy, поток, который вызывает execute, сам выполняет задачу. Это обеспечивает простой механизм управления с обратной связью, который замедляет скорость отправки новых задач.

    3. В ThreadPoolExecutor.DiscardPolicyзадача, которая не может быть выполнена, просто отбрасывается.

    4. В ThreadPoolExecutor.DiscardOldestPolicy, если исполнитель не выключен, задача во главе рабочей очереди отбрасывается, а затем повторяется попытка выполнения (что может снова привести к сбою, в результате чего это повторяется).

      Если вы хотите смоделировать CountDownLatch поведение, вы можете использовать invokeAll() метод.

  4. Еще один механизм, который вы не цитировали - это ForkJoinPool

    ForkJoinPool был добавлен в Java в Java 7. ForkJoinPool похож на Java ExecutorService но с одним отличием. ForkJoinPool позволяет задачам разделить свою работу на более мелкие задачи, которые затем передаются ForkJoinPool тоже. Кража задания происходит в ForkJoinPool когда свободные рабочие потоки крадут задачи из очереди занятых рабочих потоков.

    Java 8 представила еще один API в ExecutorService для создания пула кражи работы. Вам не нужно создавать RecursiveTask а также RecursiveAction но все еще можно использовать ForkJoinPool,

      public static ExecutorService newWorkStealingPool()
    

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

    По умолчанию в качестве параметра будет использовано количество ядер ЦП.

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

Другие вопросы по тегам