1. введение
В этой статье мы подробно обсудим основную концепцию Java – жизненный цикл потока.
Мы будем использовать краткую иллюстрированную диаграмму и, конечно же, практические фрагменты кода, чтобы лучше понять эти состояния во время выполнения потока.
Чтобы начать понимать потоки в Java, эта статья о создании потока-хорошее место для начала.
2. Многопоточность в Java
В языке Java многопоточность определяется основной концепцией потока . В течение своего жизненного цикла потоки проходят через различные состояния:
3. Жизненный цикл потока в Java
java.lang.Класс Thread содержит перечисление статических состояний , которое определяет его потенциальные состояния. В любой данный момент времени поток может находиться только в одном из этих состояний:
- НОВЫЙ – вновь созданный поток, который еще не начал выполнение
- RUNNABLE – либо работает, либо готов к выполнению, но ожидает выделения ресурсов
- ЗАБЛОКИРОВАНО – ожидание получения блокировки монитора для ввода или повторного ввода синхронизированного блока/метода
- ОЖИДАНИЕ – ожидание, пока какой-либо другой поток выполнит определенное действие без каких-либо ограничений по времени
- TIMED_WAITING – ожидание выполнения определенного действия каким-либо другим потоком в течение указанного периода
- ПРЕКРАЩЕНО – завершило свое исполнение
Все эти состояния описаны на приведенной выше диаграмме; давайте теперь подробно обсудим каждое из них.
3.1. Новые
НОВЫЙ Поток (или Рожденный Поток ) – это поток, который был создан, но еще не запущен. Он остается в этом состоянии до тех пор, пока мы не запустим его с помощью метода start () .
Следующий фрагмент кода показывает вновь созданный поток, находящийся в состоянии NEW :
Runnable runnable = new NewState(); Thread t = new Thread(runnable); Log.info(t.getState());
Поскольку мы не запустили упомянутый поток, метод t.get State() печатает:
NEW
3.2. Управляемый
Когда мы создали новый поток и вызвали для него метод start () , он перемещается из состояния NEW в состояние RUNNABLE . Потоки в этом состоянии либо запущены, либо готовы к запуску, но они ожидают выделения ресурсов из системы.
В многопоточной среде Планировщик потоков (который является частью JVM) выделяет фиксированное количество времени для каждого потока. Таким образом, он выполняется в течение определенного периода времени, а затем передает управление другим выполняемым потокам.
Например, давайте добавим метод t.start() в наш предыдущий код и попытаемся получить доступ к его текущему состоянию:
Runnable runnable = new NewState(); Thread t = new Thread(runnable); t.start(); Log.info(t.getState());
Этот код скорее всего вернет вывод в виде:
RUNNABLE
Обратите внимание, что в этом примере не всегда гарантируется , что к тому времени, когда наш элемент управления достигнет t.getState () , он все еще будет находиться в состоянии RUNNABLE .
Может случиться так, что он был немедленно запланирован планировщиком потоков и может завершить выполнение. В таких случаях мы можем получить другой результат.
3.3. Заблокирован
Поток находится в состоянии ЗАБЛОКИРОВАН , когда в данный момент он не может быть запущен. Он входит в это состояние, когда ожидает блокировки монитора и пытается получить доступ к разделу кода, заблокированному каким-либо другим потоком.
Давайте попробуем воспроизвести это состояние:
public class BlockedState { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(new DemoThreadB()); Thread t2 = new Thread(new DemoThreadB()); t1.start(); t2.start(); Thread.sleep(1000); Log.info(t2.getState()); System.exit(0); } } class DemoThreadB implements Runnable { @Override public void run() { commonResource(); } public static synchronized void commonResource() { while(true) { // Infinite loop to mimic heavy processing // 't1' won't leave this method // when 't2' try to enter this } } }
В этом кодексе:
- Мы создали два разных потока – t1 и t2
- t1 запускает и вводит синхронизированный метод commonResource () ; это означает, что только один поток может получить к нему доступ; все остальные последующие потоки, которые попытаются получить доступ к этому методу, будут заблокированы от дальнейшего выполнения до тех пор, пока текущий не завершит обработку
- Когда t1 вводит этот метод, он сохраняется в бесконечном цикле while; это просто имитирует тяжелую обработку, чтобы все остальные потоки не могли войти в этот метод
- Теперь , когда мы запускаем t2 , он пытается ввести метод common Resource () , к которому уже обращается t1, таким образом, t2 будет сохранен в ЗАБЛОКИРОВАННОМ состоянии
Находясь в этом состоянии, мы вызываем t2.getState() и получаем результат в виде:
BLOCKED
3.4. Ожидание
Поток находится в состоянии ОЖИДАНИЯ , когда он ожидает выполнения определенного действия каким-либо другим потоком. Согласно JavaDocs , любой поток может войти в это состояние, вызвав любой из следующих трех методов:
- объект.подождите()
- thread.join() или
- LockSupport.park()
Обратите внимание, что в wait() и join() – мы не определяем период ожидания, поскольку этот сценарий рассматривается в следующем разделе.
У нас есть отдельный учебник , в котором подробно обсуждается использование wait () , notify() и notifyAll() .
А пока давайте попробуем воспроизвести это состояние:
public class WaitingState implements Runnable { public static Thread t1; public static void main(String[] args) { t1 = new Thread(new WaitingState()); t1.start(); } public void run() { Thread t2 = new Thread(new DemoThreadWS()); t2.start(); try { t2.join(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); Log.error("Thread interrupted", e); } } } class DemoThreadWS implements Runnable { public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); Log.error("Thread interrupted", e); } Log.info(WaitingState.t1.getState()); } }
Давайте обсудим, что мы здесь делаем:
- Мы создали и запустили t1
- t1 создает t2 и запускает его
- В то время как обработка t2 продолжается, мы вызываем t2.join() , это помещает t1 в ОЖИДАЮЩЕЕ состояние, пока t2 не завершит выполнение
- Поскольку t1 ожидает завершения t2 , мы вызываем t1.getState() из t2
Результат здесь, как и следовало ожидать:
WAITING
3.5. Своевременное ожидание
Нить находится в TIMED_WAITING состояние, когда он ожидает, что другой поток выполнит определенное действие в течение определенного промежутка времени.
Согласно JavaDocs , есть пять способов поместить поток на TIMED_WAITING состояние:
- нить.сон(длинный миллис)
- wait(int timeout) или wait(int timeout, int nanos)
- thread.join(long millis )
- thread.join(long
- millis
Чтобы узнать больше о различиях между wait() и sleep() в Java, ознакомьтесь с этой специальной статьей здесь .
А пока давайте попробуем быстро воспроизвести это состояние:
public class TimedWaitingState { public static void main(String[] args) throws InterruptedException { DemoThread obj1 = new DemoThread(); Thread t1 = new Thread(obj1); t1.start(); // The following sleep will give enough time for ThreadScheduler // to start processing of thread t1 Thread.sleep(1000); Log.info(t1.getState()); } } class DemoThread implements Runnable { @Override public void run() { try { Thread.sleep(5000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); Log.error("Thread interrupted", e); } } }
Здесь мы создали и запустили поток t1 , который вводится в состояние сна с периодом ожидания 5 секунд; вывод будет:
TIMED_WAITING
3.6. Прекращено
Это состояние мертвой нити. Он находится в состоянии ЗАВЕРШЕН , когда он либо завершил выполнение, либо был завершен ненормально.
У нас есть специальная статья, в которой обсуждаются различные способы остановки потока.
Давайте попробуем достичь этого состояния в следующем примере:
public class TerminatedState implements Runnable { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(new TerminatedState()); t1.start(); // The following sleep method will give enough time for // thread t1 to complete Thread.sleep(1000); Log.info(t1.getState()); } @Override public void run() { // No processing in this block } }
Здесь, когда мы запустили поток t1 , самый следующий оператор Thread.sleep(1000) дает достаточно времени для завершения t1 , и поэтому эта программа выдает нам вывод в виде:
TERMINATED
В дополнение к состоянию потока мы можем проверить метод IsAlive () , чтобы определить, является ли поток живым или нет. Например, если мы вызовем метод IsAlive() в этом потоке:
Assert.assertFalse(t1.isAlive());
Он возвращает false. Проще говоря, поток жив тогда и только тогда, когда он был запущен и еще не умер.
4. Заключение
В этом уроке мы узнали о жизненном цикле потока в Java. Мы рассмотрели все шесть состояний, определенных Thread.State enum и воспроизвел их с краткими примерами.
Хотя фрагменты кода будут давать одни и те же выходные данные почти на каждой машине, в некоторых исключительных случаях мы можем получить несколько разных выходных данных, поскольку точное поведение планировщика потоков не может быть определено.
И, как всегда, используемые здесь фрагменты кода доступны на GitHub .