Рубрики
Без рубрики

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

Узнайте о жизненном цикле потока в Java.

Автор оригинала: baeldung.

1. введение

В этой статье мы подробно обсудим основную концепцию Java – жизненный цикл потока.

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

Чтобы начать понимать потоки в Java, эта статья о создании потока-хорошее место для начала.

2. Многопоточность в Java

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

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

java.lang.Класс Thread содержит перечисление статических состояний , которое определяет его потенциальные состояния. В любой данный момент времени поток может находиться только в одном из этих состояний:

  1. НОВЫЙ – вновь созданный поток, который еще не начал выполнение
  2. RUNNABLE – либо работает, либо готов к выполнению, но ожидает выделения ресурсов
  3. ЗАБЛОКИРОВАНО – ожидание получения блокировки монитора для ввода или повторного ввода синхронизированного блока/метода
  4. ОЖИДАНИЕ – ожидание, пока какой-либо другой поток выполнит определенное действие без каких-либо ограничений по времени
  5. TIMED_WAITING – ожидание выполнения определенного действия каким-либо другим потоком в течение указанного периода
  6. ПРЕКРАЩЕНО – завершило свое исполнение

Все эти состояния описаны на приведенной выше диаграмме; давайте теперь подробно обсудим каждое из них.

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
        }
    }
}

В этом кодексе:

  1. Мы создали два разных потока – t1 и t2
  2. t1 запускает и вводит синхронизированный метод commonResource () ; это означает, что только один поток может получить к нему доступ; все остальные последующие потоки, которые попытаются получить доступ к этому методу, будут заблокированы от дальнейшего выполнения до тех пор, пока текущий не завершит обработку
  3. Когда t1 вводит этот метод, он сохраняется в бесконечном цикле while; это просто имитирует тяжелую обработку, чтобы все остальные потоки не могли войти в этот метод
  4. Теперь , когда мы запускаем t2 , он пытается ввести метод common Resource () , к которому уже обращается t1, таким образом, t2 будет сохранен в ЗАБЛОКИРОВАННОМ состоянии

Находясь в этом состоянии, мы вызываем t2.getState() и получаем результат в виде:

BLOCKED

3.4. Ожидание

Поток находится в состоянии ОЖИДАНИЯ , когда он ожидает выполнения определенного действия каким-либо другим потоком. Согласно JavaDocs , любой поток может войти в это состояние, вызвав любой из следующих трех методов:

  1. объект.подождите()
  2. thread.join() или
  3. 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());
    }
}

Давайте обсудим, что мы здесь делаем:

  1. Мы создали и запустили t1
  2. t1 создает t2 и запускает его
  3. В то время как обработка t2 продолжается, мы вызываем t2.join() , это помещает t1 в ОЖИДАЮЩЕЕ состояние, пока t2 не завершит выполнение
  4. Поскольку t1 ожидает завершения t2 , мы вызываем t1.getState() из t2

Результат здесь, как и следовало ожидать:

WAITING

3.5. Своевременное ожидание

Нить находится в TIMED_WAITING состояние, когда он ожидает, что другой поток выполнит определенное действие в течение определенного промежутка времени.

Согласно JavaDocs , есть пять способов поместить поток на TIMED_WAITING состояние:

  1. нить.сон(длинный миллис)
  2. wait(int timeout) или wait(int timeout, int nanos)
  3. thread.join(long millis )
  4. thread.join(long
  5. 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 .