Executorservice java что это
Что такое ExecutorService?
Честно говоря, вопрос этот не слишком новый. Со времени выхода Java 5 и пакета java.util.concurrent.* прошло более 13 лет, но мне, за всю мою десятилетнюю практику, так ни разу и не пришлось столкнуться с этим зверем. Тем не менее, мне этот вопрос несколько раз задавали на собеседованиях и пришлось знакомиться.
Первое естественно с чего я начал это — Хабр. Но, к сожалению, нашёл здесь только две статьи:
Первая, очевидно, для тех, кто понимает и имеет опыт работы с ExecutorService. Вторая в меня, к сожалению, не вошла. Вроде небольшая и «по делу», но перечитав несколько раз я так и не понял, что же такое ExecutorService и с чем его едят. Поэтому пришлось садиться за Eclipse, курить читать javadoc и разбираться.
Итак, давайте рассмотрим простой пример:
В данном примере мы создали сам обьект ExecutorService и вызвали на нём метод execute. Передав в него самую обычную имплементацию потока. Всё это можно было бы соорудить и старым дедовским способом, но так, согласитесь, гораздо проще и изящнее. Фактически, мы быстро ответвили от текущего потока другой, асинхронный, который может что-то там выполнить в фоне.
Сам объект ExecutorService мы создали с помощью фабрики. Её методы вполне очевидны, поэтому не будем особо мусолить. Вот некоторые из них:
Помимо метода execute, который вызывается по принципу «выстрелил и забыл», наш сервис ещё имеет метод submit. Отличие от первого лишь в том, что последний возвращает объект интерфейса Future. Это просто замечательная возможность контролировать состояние потока, который мы запустили в фоне. Делается примерно так:
Обратите внимание, что метод get насмерть блокирует текущий поток и будет ждать пока фоновый не завершится. Теперь не нужно всех этих неочевидных join-ов! Если же мы боимся что наш фоновый поток не завершится никогда, можем использовать get(long,TimeUnit).
Иногда приходится из фонового потока возвратить данные в текущий. В этом тоже нам поможет метод submit, но теперь нам нужно передать в него не Runnable, а Callable обьект. По сути это два одинаковых интерфейса, отличаются только тем, что последний может возвращать что-то:
Ну вот вкратце и всё. Остались методы создания ExecutorService-а в фабрике (их там много), остались методы самого ExecutorService, вопросы об обработке ошибок в фоновых потоках, но это уже совсем другая история…
На завершение не забывайте делать:
Или не делать, в случае, что все фоновые потоки у вас будут демонами.
Описание и пример ExecutorService
В многопоточный пакет concurrent для управления потоками включено средство, называемое сервисом исполнения ExecutorService. Данное средство служит альтернативой классу Thread, предназначенному для управления потоками. В основу сервиса исполнения положен интерфейс Executor, в котором определен один метод :
При вызове метода execute исполняется поток thread. То есть, метод execute запускает указанный поток на исполнение. Следующий код показывает, как вместо обычного старта потока Thread.start() можно запустить поток с использованием сервиса исполнения :
При запуске задач с помощью Executor пакета java.util.concurrent не требуется прибегать к низкоуровневой поточной функциональности класса Thread, достаточно создать объект типа ExecutorService с нужными свойствами и передать ему на исполнение задачу типа Callable. Впоследствии можно легко просмотреть результат выполнения этой задачи с помощью объекта Future.
Интерфейс ExecutorService расширяет свойства Executor, дополняя его методами управления исполнением и контроля. Так в интерфейс ExecutorService включен метод shutdown(), позволяющий останавливать все потоки исполнения, находящиеся под управлением экземпляра ExecutorService. Также в интерфейсе ExecutorService определяются методы, которые запускают потоки исполнения FutureTask, возвращающие результаты и позволяющие определять статус остановки.
Методы интерфейса ExecutorService
Метод | Описание |
---|---|
boolean awaitTermination(long timeout, TimeUnit unit) | Блокировка до тех пор, пока все задачи не завершат выполнение после запроса на завершение работы или пока не наступит тайм-аут или не будет прерван текущий поток, в зависимости от того, что произойдет раньше |
List > invokeAll (Collection > tasks) | Выполнение задач с возвращением списка задач с их статусом и результатами завершения |
List > invokeAll (Collection > tasks, long timeout, TimeUnit unit) | Выполнение задач с возвращением списка задач с их статусом и результатами завершения в течение заданного времени |
T invokeAny(Collection > tasks) | Выполнение задач с возвращением результата успешно выполненной задачи (т. е. без создания исключения), если таковые имеются |
T invokeAny(Collection > tasks, long timeout, TimeUnit unit) | Выполнение задач в течение заданного времени с возвращением результата успешно выполненной задачи (т. е. без создания исключения), если таковые имеются |
boolean isShutdown() | Возвращает true, если исполнитель сервиса остановлен (shutdown) |
boolean isTerminated() | Возвращает true, если все задачи исполнителя сервиса завершены по команде остановки (shutdown) |
void shutdown() | Упорядоченное завершение работы, при котором ранее отправленные задачи выполняются, а новые задачи не принимаются |
List shutdownNow() | Остановка всех активно выполняемых задач, остановка обработки ожидающих задач, возвращение списка задач, ожидающих выполнения |
Future submit(Callable task) | Завершение выполнения задачи, возвращающей результат в виде объекта Future |
Future submit(Runnable task) | Завершение выполнения задачи, возвращающей объект Future, представляющий данную задачу |
Future submit(Runnable task, T result) | Завершение выполнения задачи, возвращающей объект Future, представляющий данную задачу |
Наибольший интерес в интерфейсе ExecutorService представляет метод submit(), который ставит задачу в очередь на выполнение. В качестве входного параметра данный метод принимает объект типа Callable или Runnable, а возвращает параметризованный объект типа Future, который можно использовать для доступа к результату выполнения задачи. Метод call соответствующего Callable-объекта возвращает объект Future. С использованием объекта Future можно определить завершение выполнения задачи (метод isDone()) и получить доступ к результату (метод get) или исключительной ситуации, если в процессе выполнения задачи произошла ошибка.
Стоит обратить внимание на метод shutdown(), который выполняет остановку объекта ExecutorService. Поскольку потоки в объекте ExecutorService не останавливаются сами, как обычно, поэтому их необходимо явно остановить с помощью данного метода; при этом, если в ExecutorService находятся невыполненные задачи, то потоки будут остановлены только, когда завершится последняя задача.
Пакет Concurrent включает интерфейс ScheduledExecutorService, расширяющий интерфейс ExecutorService для поддержки планирования потоков исполнения. Кроме этого, в пакет включены три предопределенных класса исполнителей: ThreadPoolExecutor, ScheduledThreadPoolExecutor и ForkJoinPool.
ThreadPoolExecutor реализует интерфейс ExecutorService и обеспечивает поддержку управляемого пула потоков исполнения. Класс ScheduledThreadPoolExecutor реализует интерфейс ScheduledExecutorService для поддержки планирования пула потоков исполнения. А класс ForkJoinPool реализует интерфейс ExecutorService и применяется в каркасе Fork/Join Framework.
Пример использования ExecutorService
Рассмотрим пример использования ExecutorService. В примере создадим фиксированный пул из двух потоков исполнения executor и четыре потока. Имплементации потоков в качестве параметра принимают объект синхронизации потоков CountDownLatch, так называемую «защелку», и текстовую строку. Сервис executor стартует все 4 потока на исполнение. Таким образом, четыре потока должны совместно использовать пул из двух потоков. Первые попавшие в пул потоки приступают к исполнению. Оставшие переходят в режим ожидания и вступают в работу по мере освобождения пула.
«Защелки» CountDownLatch используются для того, чтобы раньше времени «не выскочить» на команду завершения выполнения shutdown. Методы защелок await тормозят этот выход, переводя программу в ожидание завершения работы потока. После того, как все задачи будут выполнены, пул закрывается и программа завершает свою работу.
Вызов метода shutdown очень важен. Если его не использовать, то программа не смогла бы завершиться, поскольку исполнитель оставался бы активным. В этом можно убедиться, закомментировав вызов метода shutdown.
Результаты выполнения примера представлены ниже.
Вывод результатов деятельности потоков в консоль
В методе run в цикле в консоль выводится текст строки, номер цикла и увеличивается счетчик «защелки». Между циклами – небольшая задержка. После завершения всех циклов в консоль выводится соответствующее сообщение.
Метод «защелки» countDown увеличивает счетчик. При достижении счетчиком порогового значения (COUNT) метод await «снимает защелку». После того, как «защелки» всех потоков сняты, пул закрывается.
Обратите внимание, что, как только открылась последняя защелка, сначала сервис исполнения успел завершить работу, а после этого поток. Попробуйте самостоятельно закомментировать код с ожиданием открытия защелок (методы await) и запустить пример на исполнения. Вы должны увидеть, что строки запуска и завершения потоков окажутся рядом, но сервис executor все равно выполнит задачи всех четырех потоков.