Sdscompany.ru

Компьютерный журнал
5 просмотров
Рейтинг статьи
1 звезда2 звезды3 звезды4 звезды5 звезд
Загрузка...

Thread sleep java примеры

Потоки. Класс Thread и интерфейс Runnable

В русской терминологии за термином Thread укрепился перевод «Поток». Хотя это слово также можно перевести как «Нить». Иногда в зарубежных учебных материалах понятие потока объясняется именно на нитях. Продолжим логический ряд — там где нити, там и клубок. А где клубок, там и кот. Сразу видно, что у переводчиков не было котов. Так и возникла путаница. Тем более что существуют другие потоки под термином Stream. Переводчики, вообще странный народ.

Когда запускается любое приложение, то начинает выполняться поток, называемый главным потоком (main). От него порождаются дочерние потоки. Главный поток, как правило, является последним потоком, завершающим выполнение программы.

Несмотря на то, что главный поток создаётся автоматически, им можно управлять через объект класса Thread. Для этого нужно вызвать метод currentThread(), после чего можно управлять потоком.

Класс Thread содержит несколько методов для управления потоками.

  • getName() — получить имя потока
  • getPriority() — получить приоритет потока
  • isAlive() — определить, выполняется ли поток
  • join() — ожидать завершение потока
  • run() — запуск потока. В нём пишите свой код
  • sleep() — приостановить поток на заданное время
  • start() — запустить поток

Получим информацию о главном потоке и поменяем его имя.

Имя у главного потока по умолчанию main, которое мы заменили на CatThread.

Вызовем информацию о названии потока без указания метода.

В этом случае можно увидеть строчку Thread[main,5,main] — имя потока, его приоритет и имя его группы.

Создание собственного потока

Создать собственный поток не сложно. Достаточно наследоваться от класса Thread.

Объявим внутри нашего класса внутренний класс и вызовем его по щелчку, вызвав метод start().

Как вариант, перенести вызов метода start() в конструктор.

Создание потока с интерфейсом Runnable

Есть более сложный вариант создания потока. Для создания нового потока нужно реализовать интерфейс Runnable. Вы можете создать поток из любого объекта, реализующего интерфейс Runnable и объявить метод run().

Внутри метода run() вы размещаете код для нового потока. Этот поток завершится, когда метод вернёт управление.

Когда вы объявите новый класс с интерфейсом Runnable, вам нужно использовать конструктор:

В первом параметре указывается экземпляр класса, реализующего интерфейс. Он определяет, где начнётся выполнение потока. Во втором параметре передаётся имя потока.

После создания нового потока, его нужно запустить с помощью метода start(), который, по сути, выполняет вызов метода run().

Создадим новый поток внутри учебного проекта в виде вложенного класса и запустим его.

Внутри конструктора MyRunnable() мы создаём новый объект класса Thread

В первом параметре использовался объект this, что означает желание вызвать метод run() этого объекта. Далее вызывается метод start(), в результате чего запускается выполнение потока, начиная с метода run(). В свою очередь метод запускает цикл для нашего потока. После вызова метода start(), конструктор MyRunnable() возвращает управление приложению. Когда главный поток продолжает свою работу, он входит в свой цикл. После этого оба потока выполняются параллельно.

Можно запускать несколько потоков, а не только второй поток в дополнение к первому. Это может привести к проблемам, когда два потока пытаюсь работать с одной переменной одновременно.

Ключевое слово syncronized — синхронизированные методы

Для решения проблемы с потоками, которые могут внести путаницу, используется синхронизация.

Метод может иметь модификатор syncronized. Когда поток находится внутри синхронизированного метода, все другие потоки, которые пытаются вызвать его в том же экземпляре, должны ожидать. Это позволяет исключить путаницу, когда несколько потоков пытаются вызвать метод.

Кроме того, ключевое слово syncronized можно использовать в качестве оператора. Вы можете заключить в блок syncronized вызовы методов какого-нибудь класса:

Looper

Поток имеет в своём составе сущности Looper, Handler, MessageQueue.

Каждый поток имеет один уникальный Looper и может иметь много Handler.

Считайте Looper вспомогательным объектом потока, который управляет им. Он обрабатывает входящие сообщения, а также даёт указание потоку завершиться в нужный момент.

Поток получает свой Looper и MessageQueue через метод Looper.prepare() после запуска. Looper.prepare() идентифицирует вызывающий потк, создаёт Looper и MessageQueue и связывает поток с ними в хранилище ThreadLocal. Метод Looper.loop() следует вызывать для запуска Looper. Завершить его работу можно через метод looper.quit().

Используйте статический метод getMainLooper() для доступа к Looper главного потока:

Создадим два потока. Один запустим в основном потоке, а второй отдельно от основного. Нам будет достаточно двух кнопок и метки.

Обратите внимание, как запускаются потоки. Первый поток запускается с помощью метода start(), а второй — run(). Затем проверяем, в каком потоке мы находимся.

Эта тема достаточно сложная и для большинства не представляет интереса и необходимости изучать.

В Android потоки в чистом виде используются всё реже и реже, у системы есть собственные способы.

Многопоточность в Java – руководство с примерами

В этом руководстве мы рассмотрим, как выполняется многопоточность Java , более подробно узнаем о потоках и синхронизации между ними.

Пример одного потока :

Преимущества одного потока :

  • При выполнении одного потока снижается нагрузка на приложение;
  • Уменьшается стоимость обслуживания приложения.

Что такое многопоточность?

Многопоточность в Java — это выполнение двух или более потоков одновременно для максимального использования центрального процесса.

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

Все потоки выполняются параллельно друг другу. Для каждого отдельного потока не выделяется память, что приводит к ее экономии. Кроме этого переключение между потоками занимает меньше времени.

  • В задачах на многопоточность Java потоки выполняются независимо друг от друга, поэтому отсутствует блокирование пользователей, и можно выполнять несколько операций одновременно;
  • Одни потоки не влияют на другие, когда они наталкиваются на исключения.

Жизненный цикл потока в Java

Жизненный цикл потока :


Стадии жизни потока :

  1. Новый;
  2. Готовый к выполнению;
  3. Выполняемый;
  4. Ожидающий;
  5. Остановленный.
  1. Новый : в этой фазе поток создается с помощью класса Thread . Он остается в этом состоянии, пока программа его не запустит;
  2. Готовый к выполнению : экземпляр потока вызывается с помощью метода Start . Управление потоком предоставляется планировщику для завершения выполнения. От планировщика зависит то, следует ли запускать поток;
  3. Выполняемый : с началом выполнения потока его состояние изменяется на « выполняемый ». Планировщик выбирает один поток из пула потоков и начинает его выполнение в приложении;
  4. Ожидающий : поток ожидает своего выполнения. Поскольку в приложении выполняется сразу несколько потоков, необходимо синхронизировать их. Следовательно, один поток должен ожидать, пока другой поток не будет выполнен. Таким образом, это состояние называется состоянием ожидания;
  5. Остановленный : выполняемый поток после завершения процесса переходит в состояние « остановленный », известное также как « мертвый ».

Часто используемые методы для управления многопоточностью Java :

Читать еще:  Ljava lang string

Например : В этом примере создается поток, и применяются перечисленные выше методы.

Объяснение кода

Строка кода 2 : создаем класс « thread_Example1 «, который реализует интерфейс « Runnable » ( готовый к выполнению ). Он должен быть реализован любым классом, экземпляры которого предназначены для выполнения потоком.
Строка 4 : переопределяется метод run для готового к запуску интерфейса, так как он является обязательным при переопределении этого метода.
Строка кода 6 : определяется основной метод, в котором начнется выполнение потока.
Строка кода 7 : создается новое имя потока « guruthread1 «, инициализируя новый класс потока.
Код строка 8 : используется метод « Start » в экземпляре « guruthread1 «. Здесь поток начнет выполняться.
Строка 10 : используется метод « sleep » в экземпляре « guruthread1 «. Поток приостановит свое выполнение на 1000 миллисекунд.
Строки 9—14 : применяется метод « sleep » в блоке « try catch », так как есть проверяемое исключение « Interrupted exception ».
Строка кода 15 : для потока назначается приоритет « 1 », независимо от того, каким приоритет был до этого.
Строка кода 16 : получаем приоритет потока с помощью getPriority() .
Строка кода 17 : значение, извлеченное из getPriority .
Строка кода 18 : пишем текст, что поток выполняется.

Вывод

5 — это приоритет потока, а « Thread Running » — текст, который является выводом нашего кода.

Синхронизация потоков Java

В многопоточности Java присутствует асинхронное поведение. Если один поток записывает некоторые данные, а другой в это время их считывает, в приложении может возникнуть ошибка. Поэтому при необходимости доступа к общим ресурсам двум и более потоками используется синхронизация.

В Java есть свои методы для обеспечения синхронизации. Как только поток достигает синхронизированного блока, другой поток не может вызвать этот метод для того же объекта. Все другие потоки должны ожидать, пока текущий не выйдет из синхронизированного блока.

Таким образом, решается проблема в многопоточных приложениях. Один поток ожидает, пока другой не закончит свое выполнение, и только тогда другим потокам будет разрешено их выполнение.

Это можно написать следующим образом:

Пример многопоточности Java

В этом Java многопоточности примере мы задействуем два потока и извлекаем имена потоков.

Пример 1

Объяснение кода

Строка кода 3 : задействуем класс « GuruThread1 «, который реализует интерфейс « Runnable » ( он должен быть реализован любым классом, экземпляры которого предназначены для выполнения потоком ).
Строка 8 : основной метод класса.
Строка 9 : создаем класс Thread , экземпляр с именем « guruThread1 » и поток.
Строка 10 : создаем класс Thread , экземпляр с именем « guruThread2 » и поток.
Строка 11 : запускаем поток guruThread1 .
Строка 12 : запускаем поток guruThread2 .
Строка 13 : выводим текст « Thread names are following: «.
Строка 14 : получаем имя потока 1, используя метод getName() класса thread .
Строка кода 15 : получаем имя потока 2, используя метод getName() класса thread .

Вывод

Имена потоков выводятся как:

Пример 2

Из этого Java многопоточности урока мы узнаем о переопределяющих методах Run () и методе Start () интерфейса runnable . Создадим два потока этого класса и выполним их.

Также мы задействуем два класса:

  • Один будет реализовывать интерфейс runnable ;
  • Другой — с методом main и будет выполняться.

Объяснение кода

Строка кода 2 : принимаем класс « GuruThread2 «, содержащий метод main .
Строка 4 : принимаем основной метод класса.
Строки 6—7 : создаем экземпляр класса GuruThread3 ( создается в строках внизу ) как « threadguru1 » и запускаем поток.
Строки 8—9 : создаем еще один экземпляр класса GuruThread3 ( создается в строках внизу ) как « threadguru2 » и запускаем поток.
Строка 11 : для многопоточности Java создаем класс « GuruThread3 «, который реализует интерфейс « Runnable ». Он должен быть реализован любым классом, экземпляры которого предназначены для выполнения потоком.
Строки 13—14 : принимаем две переменные класса, из которых одна — потоковый класс, другая — строковый класс.
Строки 15—18 : переопределение конструктора GuruThread3 , который принимает один аргумент как тип String ( являющийся именем потока ). Имя будет присвоено переменной класса guruname и сохраняется имя потока.
Строка 20 : переопределяется метод run() интерфейса runnable .
Строка 21 : выводится имя потока с использованием набора команд println .
Строки 22—31 : используется цикл « for » со счетчиком, инициализированным на « 0 », который не должен быть меньше 4 . Выводится имя потока, а также выполняется приостановка потока на 1000 миллисекунд в блоке try-catch , поскольку метод sleep вызвал проверяемое исключение.
Строка 33 : переопределяется метод start интерфейса runnable .
Строка 35 : выводится текст « Thread started «.
Строки 36—40 : проверяем, содержит ли переменная класса guruthread значение. Если оно равно NULL , создается экземпляр класса thread . После этого запускается поток с использованием класса start() .

При запуске приведенного выше кода получаем следующие выходные данные:

Вывод

Поскольку у нас два потока, то мы дважды получаем сообщение « Thread started ».

Получаем соответствующие имена потоков.

Выполняется цикл, в котором печатается счетчик и имя потока, а счетчик начинается с « 0 ».

Цикл выполняется три раза, а поток приостанавливается на 1000 миллисекунд.

Следовательно, сначала мы получаем guru1 , затем guru2 и снова guru2 , поскольку процесс задерживается на 1000 миллисекунд, а дальше guru1 и снова guru1 . Процесс снова задерживается на 1000 миллисекунд, после чего мы получаем guru2 , а затем guru1 .

В этом руководстве мы рассмотрели многопоточные приложения в Java , а также то, как использовать один и несколько потоков.

  • В многопоточности пользователи не блокируются, поскольку потоки выполняются независимо друг от друга и могут осуществлять несколько операций одновременно.
  • Существует несколько стадий жизненного цикла потока:

— Новый;
— Готовый к выполнению;
— Выполняемый;
— Ожидающий;
— Остановленный.

  • Мы также узнали о синхронизации между потоками, которая позволяет приложению работать без сбоев;
  • Многопоточность упрощает выполнение многих задач приложения.

Данная публикация представляет собой перевод статьи « Multithreading in Java Tutorial with Examples » , подготовленной дружной командой проекта Интернет-технологии.ру

Многопоточное программирование в Java 8. Часть первая. Параллельное выполнение кода с помощью потоков

    Переводы, 8 июля 2015 в 16:58

Добро пожаловать в первую часть руководства по параллельному программированию в Java 8. В этой части мы на простых примерах рассмотрим, как выполнять код параллельно с помощью потоков, задач и сервисов исполнителей.

Впервые Concurrency API был представлен вместе с выходом Java 5 и с тех пор постоянно развивался с каждой новой версией Java. Большую часть примеров можно реализовать на более старых версиях, однако в этой статье я собираюсь использовать лямбда-выражения. Если вы все еще не знакомы с нововведениями Java 8, рекомендую посмотреть мое руководство.

Потоки и задачи

Все современные операционные системы поддерживают параллельное выполнение кода с помощью процессов и потоков. Процесс — это экземпляр программы, который запускается независимо от остальных. Например, когда вы запускаете программу на Java, ОС создает новый процесс, который работает параллельно другим. Внутри процессов мы можем использовать потоки, тем самым выжав из процессора максимум возможностей.

Читать еще:  Код ошибки 192 в плей маркете

Потоки (threads) в Java поддерживаются начиная с JDK 1.0. Прежде чем запустить поток, ему надо предоставить участок кода, который обычно называется «задачей» (task). Это делается через реализацию интерфейса Runnable , у которого есть только один метод без аргументов, возвращающий void — run() . Вот пример того, как это работает:

Поскольку интерфейс Runnable функциональный, мы можем использовать лямбда-выражения, которые появились в Java 8. В примере мы создаем задачу, которая выводит имя текущего потока на консоль, и запускаем ее сначала в главном потоке, а затем — в отдельном.

Результат выполнения этого кода может выглядеть так:

Из-за параллельного выполнения мы не можем сказать, будет наш поток запущен до или после вывода «Done!» на экран. Эта особенность делает параллельное программирование сложной задачей в больших приложениях.

Sportmaster Lab, Москва

Потоки могут быть приостановлены на некоторое время. Это весьма полезно, если мы хотим сэмулировать долго выполняющуюся задачу. Например, так:

Когда вы запустите этот код, вы увидите секундную задержку между выводом первой и второй строки на экран. TimeUnit — полезный класс для работы с единицами времени, но то же самое можно сделать с помощью Thread.sleep(1000) .

Работать с потоками напрямую неудобно и чревато ошибками. Поэтому в 2004 году в Java 5 добавили Concurrency API. Он находится в пакете java.util.concurrent и содержит большое количество полезных классов и методов для многопоточного программирования. С тех пор Concurrency API непрерывно развивался и развивается.

Давайте теперь подробнее рассмотрим одну из самых важных частей Concurrency API — сервис исполнителей (executor services).

Исполнители

Concurrency API вводит понятие сервиса-исполнителя (ExecutorService) — высокоуровневую замену работе с потоками напрямую. Исполнители выполняют задачи асинхронно и обычно используют пул потоков, так что нам не надо создавать их вручную. Все потоки из пула будут использованы повторно после выполнения задачи, а значит, мы можем создать в приложении столько задач, сколько хотим, используя один исполнитель.

Вот как будет выглядеть наш первый пример с использованием исполнителя:

Класс Executors предоставляет удобные методы-фабрики для создания различных сервисов исполнителей. В данном случае мы использовали исполнитель с одним потоком.

Результат выглядит так же, как в прошлый раз. Но у этого кода есть важное отличие — он никогда не остановится. Работу исполнителей надо завершать явно. Для этого в интерфейсе ExecutorService есть два метода: shutdown() , который ждет завершения запущенных задач, и shutdownNow() , который останавливает исполнитель немедленно.

Вот как я предпочитаю останавливать исполнителей:

Исполнитель пытается завершить работу, ожидая завершения запущенных задач в течение определенного времени (5 секунд). По истечении этого времени он останавливается, прерывая все незавершенные задачи.

Callable и Future

Кроме Runnable , исполнители могут принимать другой вид задач, который называется Callable . Callable — это также функциональный интерфейс, но, в отличие от Runnable , он может возвращать значение.

Давайте напишем задачу, которая возвращает целое число после секундной паузы:

Callable-задачи также могут быть переданы исполнителям. Но как тогда получить результат, который они возвращают? Поскольку метод submit() не ждет завершения задачи, исполнитель не может вернуть результат задачи напрямую. Вместо этого исполнитель возвращает специальный объект Future, у которого мы сможем запросить результат задачи.

После отправки задачи исполнителю мы сначала проверяем, завершено ли ее выполнение, с помощью метода isDone() . Поскольку задача имеет задержку в одну секунду, прежде чем вернуть число, я более чем уверен, что она еще не завершена.

Вызов метода get() блокирует поток и ждет завершения задачи, а затем возвращает результат ее выполнения. Теперь future.isDone() вернет true , и мы увидим на консоли следующее:

Задачи жестко связаны с сервисом исполнителей, и, если вы его остановите, попытка получить результат задачи выбросит исключение:

Вы, возможно, заметили, что на этот раз мы создаем сервис немного по-другому: с помощью метода newFixedThreadPool(1) , который вернет исполнителя с пулом в один поток. Это эквивалентно вызову метода newSingleThreadExecutor() , однако мы можем изменить количество потоков в пуле.

Таймауты

Любой вызов метода future.get() блокирует поток до тех пор, пока задача не будет завершена. В наихудшем случае выполнение задачи не завершится никогда, блокируя ваше приложение. Избежать этого можно, передав таймаут:

Выполнение этого кода вызовет TimeoutException :

Вы уже, возможно, догадались, почему было выброшено это исключение: мы указали максимальное время ожидания выполнения задачи в одну секунду, в то время как ее выполнение занимает две.

InvokeAll

Исполнители могут принимать список задач на выполнение с помощью метода invokeAll() , который принимает коллекцию callable-задач и возвращает список из Future .

В этом примере мы использовали функциональные потоки Java 8 для обработки задач, возвращенных методом invokeAll . Мы прошлись по всем задачам и вывели их результат на консоль. Если вы не знакомы с потоками (streams) Java 8, смотрите мое руководство.

InvokeAny

Другой способ отдать на выполнение несколько задач — метод invokeAny() . Он работает немного по-другому: вместо возврата Future он блокирует поток до того, как завершится хоть одна задача, и возвращает ее результат.

Чтобы показать, как работает этот метод, создадим метод, эмулирующий поведение различных задач. Он будет возвращать Callable , который вернет указанную строку после необходимой задержки:

Используем этот метод, чтобы создать несколько задач с разными строками и задержками от одной до трех секунд. Отправка этих задач исполнителю через метод invokeAny() вернет результат задачи с наименьшей задержкой. В данном случае это «task2»:

В примере выше использован еще один вид исполнителей, который создается с помощью метода newWorkStealingPool() . Этот метод появился в Java 8 и ведет себя не так, как другие: вместо использования фиксированного количества потоков он создает ForkJoinPool с определенным параллелизмом (parallelism size), по умолчанию равным количеству ядер машины.

ForkJoinPool впервые появился в Java 7, и мы рассмотрим его подробнее в следующих частях нашего руководства. А теперь давайте посмотрим на исполнители с планировщиком (scheduled executors).

Исполнители с планировщиком

Мы уже знаем, как отдать задачу исполнителю и получить ее результат. Для того, чтобы периодически запускать задачу, мы можем использовать пул потоков с планировщиком.

ScheduledExecutorService способен запускать задачи один или несколько раз с заданным интервалом.

Этот пример показывает, как заставить исполнитель выполнить задачу через три секунды:

Когда мы передаем задачу планировщику, он возвращает особый тип Future — ScheduledFuture , который предоставляет метод getDelay() для получения оставшегося до запуска времени.

У исполнителя с планировщиком есть два метода для установки задач: scheduleAtFixedRate() и scheduleWithFixedDelay() . Первый устанавливает задачи с определенным интервалом, например, в одну секунду:

Кроме того, он принимает начальную задержку, которая определяет время до первого запуска.

Читать еще:  Java метод append

Обратите внимание, что метод scheduleAtFixedRate() не берет в расчет время выполнения задачи. Так, если вы поставите задачу, которая выполняется две секунды, с интервалом в одну, пул потоков рано или поздно переполнится.

В этом случае необходимо использовать метод scheduleWithFixedDelay() . Он работает примерно так же, как и предыдущий, но указанный интервал будет отсчитываться от времени завершения предыдущей задачи.

В этом примере мы ставим задачу с задержкой в одну секунду между окончанием выполнения задачи и началом следующей. Начальной задержки нет, и каждая задача выполняется две секунды. Так, задачи будут запускаться на 0, 3, 6, 9 и т. д. секунде. Как видите, метод scheduleWithFixedDelay() весьма полезен, если мы не можем заранее сказать, сколько будет выполняться задача.

Это была первая часть серии статей про многопоточное программирование. Настоятельно рекомендую разобрать вышеприведенные примеры самостоятельно. Все они доступны на GitHub. Можете смело форкать репозиторий и добавлять его в избранное.

Надеюсь, вам понравилась статья. Если у вас возникли какие-либо вопросы, вы можете задать их в твиттере.

Многопоточное программирование

Большинство языков программирования поддерживают такую важную функциональность как многопоточность, и Java в этом плане не исключение. При помощи многопоточности мы можем выделить в приложении несколько потоков, которые будут выполнять различные задачи одновременно. Если у нас, допустим, графическое приложение, которое посылает запрос к какому-нибудь серверу или считывает и обрабатывает огромный файл, то без многопоточности у нас бы блокировался графический интерфейс на время выполнения задачи. А благодаря потокам мы можем выделить отправку запроса или любую другую задачу, которая может долго обрабатываться, в отдельный поток. Поэтому большинство реальных приложений, которые многим из нас приходится использовать, практически не мыслимы без многопоточности.

Класс Thread

В Java функциональность отдельного потока заключается в классе Thread . И чтобы создать новый поток, нам надо создать объект этого класса. Но все потоки не создаются сами по себе. Когда запускается программа, начинает работать главный поток этой программы. От этого главного потока порождаются все остальные дочерние потоки.

С помощью статического метода Thread.currentThread() мы можем получить текущий поток выполнения:

По умолчанию именем главного потока будет main .

Для управления потоком класс Thread предоставляет еще ряд методов. Наиболее используемые из них:

getName() : возвращает имя потока

setName(String name) : устанавливает имя потока

getPriority() : возвращает приоритет потока

setPriority(int proirity) : устанавливает приоритет потока. Приоритет является одним из ключевых факторов для выбора системой потока из кучи потоков для выполнения. В этот метод в качестве параметра передается числовое значение приоритета — от 1 до 10. По умолчанию главному потоку выставляется средний приоритет — 5.

isAlive() : возвращает true, если поток активен

isInterrupted() : возвращает true, если поток был прерван

join() : ожидает завершение потока

run() : определяет точку входа в поток

sleep() : приостанавливает поток на заданное количество миллисекунд

start() : запускает поток, вызывая его метод run()

Мы можем вывести всю информацию о потоке:

Первое main будет представлять имя потока (что можно получить через t.getName() ), второе значение 5 предоставляет приоритет потока (также можно получить через t.getPriority() ), и последнее main представляет имя группы потоков, к которому относится текущий — по умолчанию также main (также можно получить через t.getThreadGroup().getName() )

Недостатки при использовании потоков

Далее мы рассмотрим, как создавать и использовать потоки. Это довольно легко. Однако при создании многопоточного приложения нам следует учитывать ряд обстоятельств, которые негативно могут сказаться на работе приложения.

На некоторых платформах запуск новых потоков может замедлить работу приложения. Что может иметь большое значение, если нам критичная производительность приложения.

Для каждого потока создается свой собственный стек в памяти, куда помещаются все локальные переменные и ряд других данных, связанных с выполнением потока. Соответственно, чем больше потоков создается, тем больше памяти используется. При этом надо помнить, в любой системе размеры используемой памяти ограничены. Кроме того, во многих системах может быть ограничение на количество потоков. Но даже если такого ограничения нет, то в любом случае имеется естественное ограничение в виде максимальной скорости процессора.

Runnable и Thread

В Java многопоточность программы организуется с помощью интерфейса Runnable и класса Thread, который наследуется от Runnable. Первый способ более гибкий, второй – проще.

Та часть кода, которая должна выполняться в отдельном потоке, выносится в свой класс, имеющий переопределенный метод run(). Код метода run() выполняется, когда к объекту типа Thread применяется метод start(). Непосредственный вызов run() новый поток не создает.

Здесь обработка исключений необходима из-за статического метода sleep(), который приостанавливает выполнение текущего потока. Данный метод часто используют в дочерних потоках, когда они должны выполнять какое-либо действие постоянно, но не бесперебойно. Например, периодически проверять доступность ресурса.

Метод join() заставляет текущий поток ждать завершения нити, к которой применяется. Только после этого текущий поток может продолжить выполнение своего кода.

В данном случае мы создаем класс-наследник от Runnable. Объект типа Runnable или его производное передается в конструктор объекта типа Thread. После этого поток запускается.

Другой вариант – когда пользовательский класс является наследником Thread:

Этот вариант не подходит, если класс для организации отдельного потока должен наследоваться от другого класса (не Thread). Поскольку в Java нет множественного наследования классов, приходится использовать наследование от интерфейса Runnable. Также данный подход не дает возможности запускать несколько потоков на основе одного объекта. Так в первом примере мы могли бы передать единственный объект anotherRun в несколько объектов типа Thread.

Напомним, библиотечный класс Thread сам является наследником Runnable.

Если в отдельный поток обособляется небольшая подзадача, можно использовать неименованный класс:

Прерывание потоков

Для прерывания выполнения нити, если это необходимо, используется метод interrupt(), который устанавливает переменную isInterrupt в значение true. К коде пользовательского класса, унаследованного от Runnable/Thread, это переменная должна проверяться. Отсюда следует, что на самом деле в Java нет возможности прервать поток извне, поток может остановиться только сам.

С другой стороны, в метод sleep() уже встроена проверка переменной isInterrupt, поэтому проверку вручную опускают. Если sleep() считывает наличие прерывания, то генерирует исключение.

В примере основной поток ожидает ввод данных, в это время выполняется вторая нить. Но как только вы нажмете Enter, выполнится метод interrupt(). В свою очередь метод sleep() прочитает значение переменной isInterrupt класса Thread и сгенерирует исключение InterruptedException.

Если sleep() не используется, то isInterrupt проверяется вручную методом isInterrupted(). Следующий пример содержит ошибку, приводящую к зацикливанию:

Мы могли бы ожидать, что через 2 секунды сработает метод interrupt(), который прервет дочернюю нить. Однако, поскольку в ней не проверяется значение isInterrupt, цикл продолжает работать. Корректный код может выглядеть так:

При наследовании от Runnable текущий поток через this получить нельзя. Его получают, вызывая соответствующий метод класса Thread:

Ссылка на основную публикацию
Adblock
detector