Жизненный цикл потока в Java
1. Вступление
В этой статье мы подробно обсудим основную концепцию Java - жизненный цикл потока.
Мы будем использовать краткую иллюстрированную диаграмму и, конечно, практические фрагменты кода, чтобы лучше понять эти состояния во время выполнения потока.
Чтобы начать разбираться в потоках в Java, лучше всего начать сthis article при создании потока.
2. Многопоточность в Java
3. Жизненный цикл потока в Java
Классjava.lang.Thread содержитstatic State enum –, который определяет его потенциальные состояния. В любой момент времени поток может находиться только в одном из следующих состояний:
-
NEW – недавно созданный поток, который еще не начал выполнение
-
RUNNABLE – либо запущен, либо готов к выполнению, но ожидает выделения ресурсов
-
BLOCKED – ожидает получения блокировки монитора для входа или повторного входа в синхронизированный блок / метод
-
WAITING – ожидает, пока какой-либо другой поток выполнит определенное действие без ограничения по времени
-
TIMED_WAITING – ожидает, пока какой-либо другой поток выполнит определенное действие в течение указанного периода
-
TERMINATED – завершил свое выполнение
Все эти состояния показаны на диаграмме выше; Давайте теперь подробно обсудим каждый из них.
3.1. New
A NEW Thread (or a Born Thread) is a thread that’s been created but not yet started. Он остается в этом состоянии, пока мы не запустим его с помощью методаstart().
В следующем фрагменте кода показан вновь созданный поток, который находится в состоянииNEW:
Runnable runnable = new NewState();
Thread t = new Thread(runnable);
Log.info(t.getState());
Поскольку мы не запустили упомянутый поток, методt.getState() печатает:
NEW
3.2. Runnable
Когда мы создали новый поток и вызвали для него методstart(), он перешел из состоянияNEW в состояниеRUNNABLE. Threads in this state are either running or ready to run, but they’re waiting for resource allocation from the system.
В многопоточной среде планировщик потоков (который является частью JVM) выделяет фиксированное количество времени для каждого потока. Таким образом, он выполняется в течение определенного времени, а затем передает управление другим потокамRUNNABLE.
Например, давайте добавим методt.start() к нашему предыдущему коду и попытаемся получить доступ к его текущему состоянию:
Runnable runnable = new NewState();
Thread t = new Thread(runnable);
t.start();
Log.info(t.getState());
Этот кодmost likely возвращает результат как:
RUNNABLE
Обратите внимание, что в этом примере не всегда гарантируется, что к тому времени, когда наш элемент управления достигнетt.getState(), он все еще будет в состоянииRUNNABLE.
Может случиться так, что он был немедленно запланированThread-Scheduler и может завершить выполнение. В таких случаях мы можем получить другой результат.
3.3. блокированный
Поток находится в состоянииBLOCKED, когда он в настоящее время не может быть запущен. It enters this state when it is waiting for a monitor lock and is trying to access a section of code that is locked by some other thread.
Попробуем воспроизвести это состояние:
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 enters this
}
}
}
В этом коде:
-
Мы создали два разных потока -t1 иt2.
-
t1 запускается и входит в синхронизированный методcommonResource(); это означает, что к нему может получить доступ только один поток; все остальные последующие потоки, которые пытаются получить доступ к этому методу, будут заблокированы от дальнейшего выполнения, пока текущий не завершит обработку
-
Когдаt1 входит в этот метод, он остается в бесконечном цикле while; это просто имитирует тяжелую обработку, чтобы все другие потоки не могли войти в этот метод
-
Теперь, когда мы запускаемt2, он пытается ввести методcommonResource(), к которому уже обращаетсяt1,, поэтомуt2 будет сохранен в состоянииBLOCKED
Находясь в этом состоянии, мы вызываемt2.getState() и получаем результат как:
BLOCKED
3.4. Ожидание
A thread is in WAITING state when it’s waiting for some other thread to perform a particular action.According to JavaDocs, любой поток может войти в это состояние, вызвав любой из следующих трех методов:
-
object.wait()
-
thread.join() или
-
LockSupport.park()
Обратите внимание, что вwait() иjoin() - мы не определяем период тайм-аута, так как этот сценарий рассматривается в следующем разделе.
У нас естьa separate tutorial, в котором подробно обсуждается использование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, когда он ожидает, пока другой поток выполнит определенное действие в течение установленного периода времени.
According to JavaDocs, есть пять способов перевести поток в состояниеTIMED_WAITING:
-
thread.sleep(long millis)
-
wait(int timeout) илиwait(int timeout, int nanos)
-
thread.join(long миллис)
-
LockSupport.parkNanos
-
LockSupport.parkUntil
Чтобы узнать больше о различиях междуwait() иsleep() в Java, взгляните наthis dedicated article here.
А пока давайте попробуем быстро воспроизвести это состояние:
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. Отменено
Это состояние мертвой нити. It’s in the TERMINATED state when it has either finished execution or was terminated abnormally.с
У нас естьa dedicated article, в котором обсуждаются различные способы остановки потока.
Попробуем достичь этого состояния на следующем примере:
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
4. Заключение
В этом уроке мы узнали о жизненном цикле потока в Java. Мы рассмотрели все шесть состояний, определенных перечислениемThread.State, и воспроизвели их на быстрых примерах.
Хотя фрагменты кода будут давать одинаковый вывод почти на каждой машине, в некоторых исключительных случаях мы можем получить несколько разных выводов, поскольку точное поведение планировщика потоков не может быть определено.
И, как всегда, здесь используются фрагменты кодаavailable on GitHub.