Многопоточность в Java: ExecutorService

Original author: Lieven Doclo
  • Translation
В Java 5 было добавлено много вещей для организации многопоточности и особенно касаемо организации параллельного доступа. В этой и последующих статьях мы пройдемся по некоторыми из них.

ExecutorService


До Java 5 для организации работы с несколькими потоками приходилось использовать сторонние имплеменации пулинга или писать свой. С появлением ExecutorService такая необходимость отпала.

ExecutorService исполняет асинхронный код в одном или нескольких потоках. Создание инстанса ExecutorService'а делается либо вручную через конкретные имплементации (ScheduledThreadPoolExecutor или ThreadPoolExecutor), но проще будет использовать фабрики класса Executors. Например, если надо создать пул с 2мя потоками, то делается это так:
ExecutorService service = Executors.newFixedThreadPool(2);

Если требуется использовать кэширующий пул потоков, который создает потоки по мере необходимости, но переиспользует неактивные потоки (и подчищает потоки, которые были неактивные некоторое время), то это задается следующим образом:
ExecutorService service = Executors.newCachedThreadPool();

Теперь небольшой пример. Допустим, надо запустить какой-нибудь код асинхронно 10 раз. Основываясь на вышесказанном, код будет выглядеть так:
ExecutorService service = Executors.newCachedThreadPool();
for(int i = 0; i < 10; i++) {
 service.submit(new Runnable() {
  public void run() {
   // snip... piece of code
  }
 });
}


Метод submit также возвращает объект Future, который содержит информацию о статусе исполнения переданного Runnable или Callable (который может возвращать значение). Из него можно узнать выполнился ли переданный код успешно, или он еще выполняется. Вызов метода get на объекте Future возвратит значение, который возвращает Callable (или null, если используется Runnable). Метод имеет 2 checked-исключения: InterruptedException, который бросается, когда выполнение прервано через метод interrupt(), или ExecutionException если код в Runnable или Callable бросил RuntimeException, что решает проблему поддержки исключений между потоками.

ScheduledExecutorService


Иногда требуется выполнение кода асихронно и периодически или требуется выполнить код через некоторое время, тогда на помощь приходит ScheduledExecutorService. Он позволяет поставить код выполняться в одном или нескольких потоках и сконфигурировать интервал или время, на которое выполненение будет отложено. Интервалом может быть время между двумя последовательными запусками или время между окончанием одного выполнения и началом другого. Методы ScheduledExecutorService возвращают ScheduledFuture, который также содержит значение отсрочки для выполнения ScheduledFuture.

Например, если требуется отложить выполнение на 5 секунд, потребуется следующий код:
ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
service.schedule(new Runnable() { ... }, 5, TimeUnit.SECONDS);


Если требуется назначить выполнение каждую секунду:
ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
service.scheduleAtFixedRate(new Runnable() { ... }, 0, 1, TimeUnit.SECONDS);


И, наконец, если требуется назначить выполнение кода с промежутком 1 секунда между выполнениями:
ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
service.scheduleWithFixedDelay(new Runnable() { ... }, 0, 1, TimeUnit.SECONDS);

Share post

Similar posts

Comments 20

    +5
    Интересно, почему на Хабре пруд пруди вот таких выдержек из любого учебника по ЯваСкрипту, Питону, Руби и Андроиду, но практически нет по Яве и Си?
      +3
      я не понял — вы «за» или «против»?
        +3
        За
          +2
          отлично :)
        +2
        А вы прикиньте процентное соотношение профессионалов на Си и на Яве.
          +2
          Я думаю процентное соотношение в рамках каждой платформы примерно одинаково. Людей, которые по-настоящему знают Java / количество java разработчиков примерно равно аналогичной пропорции для С.
            +1
            Принцип Парето? :-)
              0
              Ага, может даже усиленный. 10% разработчиков создают 90% нетривиальных и интересных решений, продуктов, фреймворков, библиотек :)
              0
              В С++ сейчас столько наворотили, что специалистов, которые все это знают, становится все меньше. Один мой друг много лет программировал на С++ и WinAPI и даже выигрывал конкурсы Intel — решил как-то раз сдать собеседование в захудалой провинциальной конторе по С++. И не сдал!!! Просто закидали какими-то терминами и закопали буквами. Зато в Интел его взяли без проблем, так как приложения, которые они пишет выигрывают конкурсы.
            +4
            Вот только вчера вечером закончил реализацию небольшой библиотеки для многопоточности на С++ для одного кроссплатформенного фреймворка. Класс может работать как автономно, так и использовать пул потоков. Кроме того, в нём есть конвейер на основе очереди исполнения, который сильно уменьшает необходимость в объектах синхронизации, а в простых случаях делает их и вовсе ненужными. И всё это работает стабильнее, и прочее, и прочее.
            Я бы с радостью поделился и кодом, и идеями с народом, с радостью бы выслушал критику. Но где взять время для написания статей?.. Всё делается в спешном порядке, для очередных проектов. Нет возможности даже нормально ответить по комментариям к предыдущим статьям.
              +1
              Я вас отлично понимаю :(
                0
                Та же ситуация и у меня. Реализовали полноценный класс для работы с Memcacheq и демонами для него, буквально даже то, чего нет в Zend'овском, правда заточен для CakePHP — а выложить времени нет, ибо документацию оформить еще надо.
                +1
                вопрос в том, зачем нужны такие выдержки.

                Первоначальная задача «как организовать многопоточность». Мы идем в гуголь, набираем стандартный шаблон «технология+предметная область+способ реализации», в данном случае «Java Multithreading Framework» и получаем Executor в первой строчке поиска.

                ОК, допустим, первоначальная задача решена, и мы прибежали на Хабр в поисках деталей использования Executor'а. Но мы видим вырезку, ничем не более информативную, чем еще 100500 постов в блогах на ту же самую тему, не рассказывающую ничего нового по сравнению даже с тем же жавадоком по Executor'у.

                Знаете, одно время продавались толстые книги про Яву и Си, которые ничем не отличались от жавадока. Поэтому их никто не читал и не покупал. Наверное поэтому они потихоньку перевелись совсем ;)

                Я за, но в статью нужно срочно вставить изюминку!
                  +1
                  Так можно прийти к выводу, что любой текстовый материал по стандартной библиотеке вторичен по сравнению с самой стандартной библиотекой :) (Вместо жавадока, кстати, зачастую реально удобнее исходники jdk смотреть :) Задача — ускорить освоение, нигде не наврав и не потеряв смысл. Если есть изюминка — так совсем замечательно.
                    +1
                    просто я зануда.

                    (Знаю несколько интересных вещей, которые можно написать про Executor. Точнее, костылей. И меня печалит, что в статье нет ни одного сочного костыля или яркого решения из собственной практики автора.)
                +6
                В свое время пару лет назад «пересаживал» один проект с «голых» потоков (wait(), notifyAll()) на java.util.concurrent. Не пожалел ни минуты потраченного времени — код стал гораздо чище, понятнее другим разработчикам (особенно тем, кто только-только приходил в проект), а самое главное — избавились от всех дедлоков и лайвлоков.

                Так что всем, кто до сих пор увяз в старом конкурентном коде, я настоятельно советую посмотреть хотябы в сторону j.u.c (а лучше — Clojure и/или Akka).

                Тем, кто по какой-то причине не может перейти на Java 5 (да, знаю полно таких мест), напоминаю, что есть вполне рабочий и проверенный временем бэкпорт: backport-jsr166.sourceforge.net/
                  0
                  Ну так поделитесь же опытом: что на что меняли и почему.
                  0
                  Странно, что почти во всех примерах по многопоточности Java нет даже простых вычислений рациональности применения потоков.
                  типа int cores = Runtime.getRuntime().availableProcessors();

                  Может в одном потоке будет быстрее работать, так как не надо будет ждать блокировок
                    0
                    Побуду немного кэпом. Причин использования многопоточности вагон и маленькая тележка и использование использование многопоточности для ускорения вычислений это одна из множества областей применения.

                    Простой пример предположим у тебя всего одно ядро. Есть основной поток, во время работы которого возникло событие о котором надо отправить отчет на удаленный сервер. Если ты все делаешь в одном потоке то пока программа подключится к удаленному серверу (а пинг иногда может и в сотнях мс измеряться) пока выгрузит данные, пока еще как то там отреагирует на событие. Все это время основной поток простаивает. А если еще есть проблемы со интенет соединением, то он будет пытаться еще несколько попыток отаравки сделать. Между которыми еще таймаут выждет. И все это время работа программы парализована. Поэтому мы создаем отдельный поток и обработку события выносим в отдельный поток. Все. Программа может дальше заниматься своими делами, а обработчик события может неспешно пытаться отправить этот файл на удаленный сервер. И даже несмотря на то, что у тебя всего навсего одно ядро, то есть ни о каком истинно паралельном вычислении быть не может. Но так как обработчик события все равно большую часть времени ждет выполнения операций ввода вывода, то никаких проблем это не создает и программа работает лучше.
                      0
                      Что-то делать параллельно с каким-нибудь life lock — это почти единственный очевидный бенефит, если на deadlock не наступишь. Все остальное отягощено спецэффектами, коих миллион. Я пока на джаве делал параллельное выполнение, совмещенное с GUI — потратил кучу нервов, и так до конца не получилось без глюков обновлять картинку на форме, которая рисуется параллельно.

                  Only users with full accounts can post comments. Log in, please.