25 апреля 2019
Тэги: Java, многопоточность.
Есть два способа создания потоков в Java: унаследоваться от класса Thread или реализовать интерфейс Runnable.
Рассмотрим пример, в котором мы расширяем стандартный класс Thread, переопределяя лишь один метод run(). В нём мы ждём 3 секунды, а затем выводим сообщение о завершении потока. Обратите внимание, что запуск потока осуществляется не через метод run(), а через метод start().
Метод TimeUnit.SECONDS.sleep() полностью эквивалентен стандартному Thread.sleep(), однако в первом случае мы явно указываем единицы измерения времени, что делает код более читаемым. Я рекомендую использовать именно такую форму записи.
Поскольку метод sleep() относится к низкоуровневым, он может кидать InterruptedException, которое свидетельствует о том, что поток был прерван. Это исключение является единственным признаком того, что поток был принудительно остановлен. И чтобы не терять эту информацию в стеке вызовов, мы выставляем флаг interrupted при помощи соответствующего метода Thread.currentThread(). Такой способ обработки InterruptedException является правильным и именно его нужно использовать в подобных случаях.
В результате запуска второго потока мы сначала увидим сообщение о том, что основной поток завершён, а затем будем дожидаться завершения второго потока.
Теперь создадим поток с помощью интерфейса Runnable.
Интерфейс Runnable требует определить единственный метод run(). Для запуска потока требуется создать объект Thread, передав ему в конструктор экземпляр нашего класса. Затем у этого объекта Thread вызываем метод start(). Данный вариант является более предпочтительным, если наш класс уже наследуется от какого-то другого базового класса.
В Java процесс завершается тогда, когда завершается последний его поток. Даже если метод main() уже завершился, но еще выполняются порожденные им потоки, система будет ждать их завершения.
Однако это правило не относится к особому виду потоков – демонам. Если завершился последний обычный поток процесса, и остались только потоки-демоны, то они будут принудительно завершены и выполнение процесса закончится. Чаще всего потоки-демоны используются для выполнения фоновых задач, обслуживающих процесс в течение его жизни.
Сделать поток демоном очень просто – достаточно установить соответствующий флаг с помощью метода setDaemon().
Несмотря на то, что поток-демон останавливается на три секунды, мы эти три секунды ждать не будем и программа завершит свою работу. При это исключение InterruptedException не возникает.
Остановить поток можно с помощью метода interrupt(). В случае, если поток не является демоном, то возникает исключение InterruptedException, о котором мы говорили выше.
Но вызов interrupt() следует толковать не как принудительную остановку, а как вежливый запрос. Немного изменим пример, чтобы показать, как должна обрабатываться остановка. Для имитации тяжёлой задачи сделаем цикл в котором будем вычислять тригонометрическую функцию.
Перед каждой итерацией цикла мы проверяем флаг Thread.currentThread().isInterrupted(). И если он оказывается установлен, мы прерываем наши вычисления. Таким образом, мы корректно обрабатываем флаг остановки потока.
Kotlin, Java, Spring, Spring Boot, Spring Data, SQL, PostgreSQL, Oracle, Linux, Hibernate, Collections, Stream API, многопоточность, файлы, Nginx, Apache, maven, gradle, JUnit, YouTube, новости, руководство, ООП, алгоритмы, головоломки, rest, GraphQL, Excel, XML, json, yaml.