Каковы преимущества использования ExecutorService?

В чем преимущество использования ExecutorService над бегущими потоками, проходящими Runnable в Thread конструктор?

9 ответов

Решение

ExecutorService устраняет многие сложности, связанные с абстракциями более низкого уровня, такими как raw Thread, Он предоставляет механизмы для безопасного запуска, закрытия, отправки, выполнения и блокировки при успешном или внезапном завершении задач (выражается как Runnable или же Callable).

Из JCiP, раздел 6.2, прямо изо рта лошади:

Executor может быть простым интерфейсом, но он формирует основу для гибкой и мощной структуры для асинхронного выполнения задач, которая поддерживает широкий спектр политик выполнения задач. Он обеспечивает стандартные средства отделения представления задачи от выполнения задачи, описывая задачи как Runnable, Executor Реализации также предоставляют поддержку жизненного цикла и ловушки для добавления сбора статистики, управления приложениями и мониторинга. ... используя Executor обычно это самый простой путь к реализации дизайна "производитель-потребитель" в вашем приложении.

Вместо того, чтобы тратить свое время на реализацию (часто неправильно и с большими усилиями) базовой инфраструктуры для параллелизма, j.u.concurrent Фреймворк позволяет вместо этого сосредоточиться на структурировании задач, зависимостей, потенциального параллелизма. Для большого количества параллельных приложений легко определить и использовать границы задач и использовать j.u.c что позволяет вам сосредоточиться на гораздо меньшем подмножестве реальных проблем параллелизма, которые могут потребовать более специализированных решений.

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

Разработчики, вероятно, уже понимают стандартные библиотечные классы, поэтому нет необходимости изучать API и поведение параллельных компонентов ad-hoc. Кроме того, параллельные приложения гораздо проще отлаживать, если они построены на надежных, хорошо протестированных компонентах.

Этот вопрос о SO спрашивает о хорошей книге, на которую немедленным ответом является JCiP. Если вы еще этого не сделали, получите себе копию. Представленный здесь комплексный подход к параллелизму выходит далеко за рамки этого вопроса и в долгосрочной перспективе избавит вас от душевных страданий.

Я вижу преимущество в управлении / планировании нескольких потоков. С ExecutorService вам не нужно писать собственный менеджер потоков, который может быть подвержен ошибкам. Это особенно полезно, если вашей программе нужно запускать несколько потоков одновременно. Например, вы хотите выполнять два потока одновременно, вы можете легко сделать это так:

ExecutorService exec = Executors.newFixedThreadPool(2);

exec.execute(new Runnable() {
  public void run() {
    System.out.println("Hello world");
  }
});

exec.shutdown();

Пример может быть тривиальным, но постарайтесь думать, что строка "hello world" состоит из тяжелой операции, и вы хотите, чтобы эта операция выполнялась в нескольких потоках одновременно, чтобы повысить производительность вашей программы. Это только один пример, все еще есть много случаев, когда вы хотите запланировать или запустить несколько потоков и использовать ExecutorService в качестве менеджера потоков.

Для запуска одного потока я не вижу явного преимущества использования ExecutorService.

Следующие ограничения по сравнению с традиционным потоком преодолеваются платформой Executor (встроенной платформой пула потоков).

  • Плохое управление ресурсами, т.е. оно продолжает создавать новый ресурс для каждого запроса. Нет ограничений на создание ресурса. Используя структуру Executor, мы можем повторно использовать существующие ресурсы и ограничить их создание.
  • Не Надежный: если мы продолжим создавать новую тему, мы получим StackruException исключение, следовательно, наша JVM потерпит крах.
  • Накладные расходы Создание времени: для каждого запроса нам нужно создать новый ресурс. На создание нового ресурса уходит много времени. т.е. создание темы> задача. Используя фреймворк Executor, мы можем получить встроенный пул потоков.

Преимущества пула потоков

  • Использование пула потоков сокращает время ответа, избегая создания потоков во время обработки запросов или задач.

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

  • Пул потоков в Java-приложении повышает стабильность системы, создавая настроенное количество потоков, определяемое исходя из загрузки системы и доступного ресурса.

  • Thread Pool освобождает разработчика приложений от управления потоками и позволяет сосредоточиться на бизнес-логике.

Источник

Ниже приведены некоторые преимущества:

  1. Служба Executor управляет потоком асинхронно
  2. Используйте callable, чтобы получить результат возврата после завершения потока.
  3. Управлять распределением работ по свободным потокам и перепродавать выполненные работы из потоков для автоматического назначения новых работ
  4. fork - объединить фреймворк для параллельной обработки
  5. Лучшее общение между потоками
  6. invokeAll и invokeAny дают больше контроля для запуска любого или всех потоков одновременно
  7. завершение работы обеспечивает возможность завершения всех назначенных потоков
  8. Служба Scheduled Executor Services предоставляет методы для создания повторяющихся вызовов runnable и вызываемых объектов. Надеюсь, это поможет вам

Это действительно так дорого, чтобы создать новую тему?

В качестве эталона я только что создал 60000 потоков с Runnableс пустыми run() методы. После создания каждого потока я назвал его start(..) метод немедленно. Это заняло около 30 секунд интенсивной работы процессора. Подобные эксперименты были сделаны в ответ на этот вопрос. Суть в том, что если потоки не завершаются немедленно и накапливается большое количество активных потоков (несколько тысяч), то возникнут проблемы: (1) у каждого потока есть стек, поэтому у вас не хватит памяти (2) может существовать ограничение на количество потоков на процесс, налагаемое ОС, но это не обязательно, кажется.

Итак, насколько я вижу, если мы говорим о запуске, скажем, 10 потоков в секунду, и все они заканчиваются быстрее, чем запускаются новые, и мы можем гарантировать, что эта скорость не будет превышена слишком сильно, тогда ExecutorService не дает каких-либо конкретных преимуществ в видимой производительности или стабильности. (Хотя это все же может сделать его более удобным или читабельным для выражения определенных идей параллелизма в коде.) С другой стороны, если вы можете планировать сотни или тысячи задач в секунду, на выполнение которых требуется время, вы можете столкнуться с большими проблемами. сразу. Это может произойти неожиданно, например, если вы создаете потоки в ответ на запросы к серверу, и наблюдается резкое увеличение интенсивности запросов, которые получает ваш сервер. Но, например, один поток в ответ на каждое событие пользовательского ввода (нажатие клавиши, движение мыши) кажется вполне подходящим, если задачи короткие.

ExecutorService также предоставляет доступ к FutureTask, который вернет вызывающему классу результаты фоновой задачи после завершения. В случае реализации Callable

public class TaskOne implements Callable<String> {

@Override
public String call() throws Exception {
    String message = "Task One here. . .";
    return message;
    }
}

public class TaskTwo implements Callable<String> {

@Override
public String call() throws Exception {
    String message = "Task Two here . . . ";
    return message;
    }
}

// from the calling class

ExecutorService service = Executors.newFixedThreadPool(2);
    // set of Callable types
    Set<Callable<String>>callables = new HashSet<Callable<String>>();
    // add tasks to Set
    callables.add(new TaskOne());
    callables.add(new TaskTwo());
    // list of Future<String> types stores the result of invokeAll()
    List<Future<String>>futures = service.invokeAll(callables);
    // iterate through the list and print results from get();
    for(Future<String>future : futures) {
        System.out.println(future.get());
    }

До версии java 1.5 Thread/Runnable был разработан для двух отдельных сервисов.

  1. Единица работы
  2. Выполнение этой единицы работы

ExecutorService отделяет эти две службы, определяя Runnable/Callable как единицу работы и Executor как механизм для выполнения (с жизненным циклом) единицы работы

Платформа исполнителя

      //Task
Runnable someTask = new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello World!");
    }
};

//Thread
Thread thread = new Thread(someTask);
thread.start();

//Executor 
Executor executor = new Executor() {
    @Override
    public void execute(Runnable command) {
        Thread thread = new Thread(someTask);
        thread.start();
    }
};

это просто интерфейс, который принимает файлы . execute()метод может просто вызвать command.run()или работа с другими классами, которые используют Runnable(например, нить)

      interface Executor
    execute(Runnable command)

интерфейс, который расширяет Executorи добавляет методы для управления - shutdown()а также submit()который возвращает Future [О] - get(), cancel()

      interface ExecutorService extends Executor 
    Future<?> submit(Runnable task)
    shutdown()
    ...

ScheduledExecutorServiceрасширяется для планирования выполнения задач

      interface ScheduledExecutorService extends ExecutorService
    schedule()

Executorsкласс, который является фабрикой для предоставления ExecutorServiceреализации для бега asyncзадачи [О программе]

      class Executors 
    newFixedThreadPool() returns ThreadPoolExecutor
    newCachedThreadPool() returns ThreadPoolExecutor
    newSingleThreadExecutor() returns FinalizableDelegatedExecutorService
    newWorkStealingPool() returns ForkJoinPool
    newSingleThreadScheduledExecutor() returns DelegatedScheduledExecutorService
    newScheduledThreadPool() returns ScheduledThreadPoolExecutor
    ...

Вывод

Работаю с Threadэто дорогостоящая операция для процессора и памяти. ThreadPoolExecutorсостоит из очереди задач ( BlockingQueue) и пул потоков (набор Worker), которые имеют лучшую производительность и API для обработки асинхронных задач.

Создание большого количества потоков без ограничения максимального порога может привести к нехватке памяти в приложении. Из-за этого создание ThreadPool является гораздо лучшим решением. Используя ThreadPool, мы можем ограничить количество потоков, которые могут быть объединены и использованы повторно.

Фреймворк Executors облегчает процесс создания пулов потоков в Java. Класс Executors обеспечивает простую реализацию ExecutorService с использованием ThreadPoolExecutor.

Источник:

Что такое Executors Framework

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