1. Введение
В этой статье мы подробно обсудим основную концепцию Java — жизненный цикл потока.
Мы будем использовать краткую иллюстрированную диаграмму и, конечно же, практические фрагменты кода, чтобы лучше понять эти состояния во время выполнения потока.
Чтобы начать понимать потоки в Java, эта статья о создании потока является хорошей отправной точкой.
2. Многопоточность в Java
В языке Java многопоточность управляется основной концепцией Thread . В течение своего жизненного цикла потоки проходят через различные состояния:
3. Жизненный цикл потока в Java
Класс java.lang.Thread
содержит статическое перечисление состояний
, которое определяет его потенциальные состояния. В любой момент времени поток может находиться только в одном из следующих состояний:
NEW —
вновь созданный поток, который еще не начал выполнениеRUNNABLE —
либо запущен, либо готов к выполнению, но ожидает выделения ресурсовЗАБЛОКИРОВАНО —
ожидание получения блокировки монитора для входа или повторного входа в синхронизированный блок/методWAITING —
ожидание выполнения какого-либо другого потока определенного действия без ограничения времени.TIMED_WAITING —
ожидание того, что какой-то другой поток выполнит определенное действие в течение указанного периода.TERMINATED –
завершил выполнение
Все эти состояния показаны на диаграмме выше; давайте теперь обсудим каждый из них подробно.
3.1. Новый
НОВАЯ нить
(или рожденная нить
)
— это нить, которая была создана, но еще не запущена.
Он остается в этом состоянии до тех пор, пока мы не запустим его с помощью метода start()
.
В следующем фрагменте кода показан только что созданный поток, находящийся в состоянии NEW
:
Runnable runnable = new NewState();
Thread t = new Thread(runnable);
Log.info(t.getState());
Поскольку мы не запустили указанный поток, метод t.getState()
выводит:
NEW
3.2. Запускаемый
Когда мы создали новый поток и вызвали для него метод start()
, он перешел из состояния NEW
в состояние RUNNABLE
. Потоки в этом состоянии либо запущены, либо готовы к запуску, но ожидают выделения ресурсов системой.
В многопоточной среде планировщик потоков (который является частью JVM) выделяет фиксированное количество времени для каждого потока. Таким образом, он выполняется определенное время, а затем передает управление другим RUNNABLE
потокам.
Например, давайте добавим метод t.start()
в наш предыдущий код и попробуем получить доступ к его текущему состоянию:
Runnable runnable = new NewState();
Thread t = new Thread(runnable);
t.start();
Log.info(t.getState());
Этот код, скорее всего , вернет вывод как:
RUNNABLE
Обратите внимание, что в этом примере не всегда гарантируется, что к тому времени, когда наш элемент управления достигнет t.getState()
, он все еще будет находиться в состоянии RUNNABLE
.
Может случиться так, что он был немедленно запланирован планировщиком потоков
и может закончить выполнение. В таких случаях мы можем получить другой результат.
3.3. Заблокировано
Поток находится в состоянии BLOCKED
, если в настоящее время он не может быть запущен. Он входит в это состояние, когда ожидает блокировки монитора и пытается получить доступ к разделу кода, заблокированному другим потоком.
Попробуем воспроизвести это состояние:
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
, он пытается войти вметод commonResource()
, к которому уже обращаетсяt1,
таким образом,t2
будет оставаться в состоянииBLOCKED .
Находясь в этом состоянии, мы вызываем t2.getState()
и получаем результат в виде:
BLOCKED
3.4. Ожидающий
Поток находится в состоянии WAITING
, когда он ожидает, пока другой поток выполнит определенное действие. Согласно 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
в состояниеWAITING
до тех пор, покаt2
не завершит выполнение. - Поскольку
t1
ожидает завершенияt2
, мы вызываемt1.getState()
изt2 .
Вывод здесь, как и следовало ожидать:
WAITING
3.5. Ожидание по времени
Поток находится в состоянии TIMED_WAITING
, когда он ожидает, пока другой поток выполнит определенное действие в течение заданного периода времени.
Согласно JavaDocs , существует пять способов перевести поток в состояние TIMED_WAITING
:
thread.sleep (длинный миллис)
ожидание (целое время ожидания)
илиожидание (целое время ожидания, целое число nanos)
thread.join (длинный
миллис)
LockSupport.parkNanos
LockSupport.parkUntil
Чтобы узнать больше о различиях между 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. Прекращено
Это состояние мертвого потока. Он находится в состоянии TERMINATED
, когда либо завершил выполнение, либо был аварийно завершен.
У нас есть специальная статья , в которой обсуждаются различные способы остановки потока.
Попробуем достичь этого состояния на следующем примере:
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());
Он возвращает ложь.
Проще говоря, поток жив тогда и только тогда, когда он был запущен и еще не умер.
4. Вывод
В этом руководстве мы узнали о жизненном цикле потока в Java. Мы рассмотрели все шесть состояний, определенных перечислением Thread.State
, и воспроизвели их на кратких примерах.
Хотя фрагменты кода будут давать один и тот же результат почти на каждой машине, в некоторых исключительных случаях мы можем получить разные результаты, поскольку невозможно определить точное поведение планировщика потоков.
И, как всегда, используемые здесь фрагменты кода доступны на GitHub .