Честно говоря, вопрос этот не слишком новый. Со времени выхода Java 5 и пакета java.util.concurrent.* прошло более 13 лет, но мне, за всю мою десятилетнюю практику, так ни разу и не пришлось столкнуться с этим зверем. Тем не менее, мне этот вопрос несколько раз задавали на собеседованиях и пришлось знакомиться.
Первое естественно с чего я начал это — Хабр. Но, к сожалению, нашёл здесь только две статьи:
habrahabr.ru/post/260953
habrahabr.ru/post/116363
Первая, очевидно, для тех, кто понимает и имеет опыт работы с ExecutorService. Вторая в меня, к сожалению, не вошла. Вроде небольшая и «по делу», но перечитав несколько раз я так и не понял, что же такое ExecutorService и с чем его едят. Поэтому пришлось садиться за Eclipse,курить читать javadoc и разбираться.
Итак, давайте рассмотрим простой пример:
В данном примере мы создали сам обьект ExecutorService и вызвали на нём метод execute. Передав в него самую обычную имплементацию потока. Всё это можно было бы соорудить и старым дедовским способом, но так, согласитесь, гораздо проще и изящнее. Фактически, мы быстро ответвили от текущего потока другой, асинхронный, который может что-то там выполнить в фоне.
Сам объект ExecutorService мы создали с помощью фабрики. Её методы вполне очевидны, поэтому не будем особо мусолить. Вот некоторые из них:
Помимо метода execute, который вызывается по принципу «выстрелил и забыл», наш сервис ещё имеет метод submit. Отличие от первого лишь в том, что последний возвращает объект интерфейса Future. Это просто замечательная возможность контролировать состояние потока, который мы запустили в фоне. Делается примерно так:
Обратите внимание, что метод get насмерть блокирует текущий поток и будет ждать пока фоновый не завершится. Теперь не нужно всех этих неочевидных join-ов! Если же мы боимся что наш фоновый поток не завершится никогда, можем использовать get(long,TimeUnit).
Иногда приходится из фонового потока возвратить данные в текущий. В этом тоже нам поможет метод submit, но теперь нам нужно передать в него не Runnable, а Callable обьект. По сути это два одинаковых интерфейса, отличаются только тем, что последний может возвращать что-то:
Ну вот вкратце и всё. Остались методы создания ExecutorService-а в фабрике (их там много), остались методы самого ExecutorService, вопросы об обработке ошибок в фоновых потоках, но это уже совсем другая история…
На завершение не забывайте делать:
Или не делать, в случае, что все фоновые потоки у вас будут демонами.
Первое естественно с чего я начал это — Хабр. Но, к сожалению, нашёл здесь только две статьи:
habrahabr.ru/post/260953
habrahabr.ru/post/116363
Первая, очевидно, для тех, кто понимает и имеет опыт работы с ExecutorService. Вторая в меня, к сожалению, не вошла. Вроде небольшая и «по делу», но перечитав несколько раз я так и не понял, что же такое ExecutorService и с чем его едят. Поэтому пришлось садиться за Eclipse,
Итак, давайте рассмотрим простой пример:
ExecutorService service = Executors.newFixedThreadPool(3); service.execute(new Runnable() { public void run() { System.out.println("Another thread was executed"); } });
В данном примере мы создали сам обьект ExecutorService и вызвали на нём метод execute. Передав в него самую обычную имплементацию потока. Всё это можно было бы соорудить и старым дедовским способом, но так, согласитесь, гораздо проще и изящнее. Фактически, мы быстро ответвили от текущего потока другой, асинхронный, который может что-то там выполнить в фоне.
Сам объект ExecutorService мы создали с помощью фабрики. Её методы вполне очевидны, поэтому не будем особо мусолить. Вот некоторые из них:
ExecutorService service1 = Executors.newSingleThreadExecutor(); ExecutorService service2 = Executors.newFixedThreadPool(3); ExecutorService service3 = Executors.newScheduledThreadPool(3);
Помимо метода execute, который вызывается по принципу «выстрелил и забыл», наш сервис ещё имеет метод submit. Отличие от первого лишь в том, что последний возвращает объект интерфейса Future. Это просто замечательная возможность контролировать состояние потока, который мы запустили в фоне. Делается примерно так:
Future future = service.submit(new Runnable() { public void run() { System.out.println("Another thread was executed"); } }); ... future.get();
Обратите внимание, что метод get насмерть блокирует текущий поток и будет ждать пока фоновый не завершится. Теперь не нужно всех этих неочевидных join-ов! Если же мы боимся что наш фоновый поток не завершится никогда, можем использовать get(long,TimeUnit).
Иногда приходится из фонового потока возвратить данные в текущий. В этом тоже нам поможет метод submit, но теперь нам нужно передать в него не Runnable, а Callable обьект. По сути это два одинаковых интерфейса, отличаются только тем, что последний может возвращать что-то:
Future future = service.submit(new Callable(){ public Object call() throws Exception { System.out.println("Another thread was executed"); return "result"; } }); ... System.out.println("Result: " + future.get());
Ну вот вкратце и всё. Остались методы создания ExecutorService-а в фабрике (их там много), остались методы самого ExecutorService, вопросы об обработке ошибок в фоновых потоках, но это уже совсем другая история…
На завершение не забывайте делать:
service.shutdown();
Или не делать, в случае, что все фоновые потоки у вас будут демонами.
