Каковы преимущества использования 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 освобождает разработчика приложений от управления потоками и позволяет сосредоточиться на бизнес-логике.
Ниже приведены некоторые преимущества:
- Служба Executor управляет потоком асинхронно
- Используйте callable, чтобы получить результат возврата после завершения потока.
- Управлять распределением работ по свободным потокам и перепродавать выполненные работы из потоков для автоматического назначения новых работ
- fork - объединить фреймворк для параллельной обработки
- Лучшее общение между потоками
- invokeAll и invokeAny дают больше контроля для запуска любого или всех потоков одновременно
- завершение работы обеспечивает возможность завершения всех назначенных потоков
- Служба 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 был разработан для двух отдельных сервисов.
- Единица работы
- Выполнение этой единицы работы
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.
Источник: