Говоря о состоянии потока Java

Java

предисловие

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

Сегодня мы подробно поговорим об этих состояниях и при каких обстоятельствах произойдет переход.

1. Статус потока

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

public class Thread {

    public enum State {

        /**
         * 新建状态
         * 创建后尚未启动的线程
         */
        NEW,

        /**
         * 运行状态
         * 包括正在执行,也可能正在等待操作系统为它分配执行时间
         */
        RUNNABLE,

        /**
         * 阻塞状态
         * 一个线程因为等待临界区的锁被阻塞产生的状态
         */
        BLOCKED,

        /**
         * 无限期等待状态
         * 线程不会被分配处理器执行时间,需要等待其他线程显式唤醒
         */
        WAITING,

        /**
         * 限期等待状态
         * 线程不会被分配处理器执行时间,但也无需等待被其他线程显式唤醒
         * 在一定时间之后,它们会由操作系统自动唤醒
         */
        TIMED_WAITING,

        /**
         * 结束状态
         * 线程退出或已经执行完成
         */
        TERMINATED;
    }
}

2. Переход состояния

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

1. Новый

Новое состояние — самое простое, после создания потока он находится в этом состоянии до своего запуска.

public static void main(String[] args) {
    Thread thread = new Thread("新建线程");
    System.out.println("线程状态:"+thread.getState());
}
-- 输出:线程状态:NEW

2. Беги

Состояние исполняемого потока, когда мы вызываемstart()метод, поток выполняется в виртуальной машине Java, но может ожидать других ресурсов от операционной системы (например, процессора).

Таким образом, здесь задействованы два состояния:Running 和 Ready, вместе именуемыеRunnable. Почему это?

Это связано с проблемой планирования потоков Java:

Планирование потоков относится к процессу, с помощью которого система распределяет права использования процессора для потоков. Существует два основных метода планирования: совместное планирование потоков и вытесняющее планирование потоков.

  • Совместное планирование потоков

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

  • Упреждающее планирование потоков

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

Какой из них лучше или хуже — выходит за рамки данной статьи. Нам просто нужно знать, что метод планирования потоков, используемый в Java, является упреждающим планированием.

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

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

public static void main(String[] args) {
	
    Thread thread = new Thread(() -> {
        for (;;){}
    });
    thread.start();
    System.out.println("线程状态:"+thread.getState());
}
-- 输出:线程状态:RUNNABLE

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

Давайте рассмотрим два примера классического блокирующего ввода-вывода:

public static void main(String[] args) throws Exception {

    Thread t1 = new Thread(() -> {
        try {
            ServerSocket serverSocket = new ServerSocket(9999);
            while (true){
                Socket socket = serverSocket.accept();
                OutputStream outputStream = socket.getOutputStream();
                outputStream.write("Hello".getBytes());
                outputStream.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    },"accept");
    t1.start();

    Thread t2 = new Thread(() -> {
        try {
            Socket socket = new Socket("127.0.0.1",9999);
            for (;;){
                InputStream inputStream = socket.getInputStream();
                byte[] bytes = new byte[5];
                inputStream.read(bytes);
                System.out.println(new String(bytes));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    },"read");
    t2.start();
}

В приведенном выше коде мы знаем, чтоserverSocket.accept()иinputStream.read(bytes);Оба метода являются блокирующими.

Один из них ожидает подключения клиента, другой — поступления данных. Однако состояние двух потоковRUNNABLEиз.

"read" #13 prio=5 os_prio=0 tid=0x0000000023f6c800 nid=0x1cd0 runnable [0x0000000024b3e000]
   java.lang.Thread.State: RUNNABLE
	at java.net.SocketInputStream.socketRead0(Native Method)
	at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
	at java.net.SocketInputStream.read(SocketInputStream.java:171)
	at java.net.SocketInputStream.read(SocketInputStream.java:141)
"accept" #12 prio=5 os_prio=0 tid=0x0000000023f68000 nid=0x4cec runnable [0x0000000024a3e000]
   java.lang.Thread.State: RUNNABLE
	at java.net.DualStackPlainSocketImpl.accept0(Native Method)
	at java.net.DualStackPlainSocketImpl.socketAccept(DualStackPlainSocketImpl.java:131)
	at java.net.AbstractPlainSocketImpl.accept(AbstractPlainSocketImpl.java:409)
	at java.net.PlainSocketImpl.accept(PlainSocketImpl.java:199)

что насчет этого ?

Как мы уже говорили,处于 Runnable 状态下的线程,正在 Java 虚拟机中执行,但它可能正在等待来自操作系统(如处理器)的其他资源.

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

Не стоит недооценивать этот вопрос, он очень сбивает с толку. Если некоторые интервьюеры спросят,如果一个线程正在进行阻塞式 I/O 操作时,它处于什么状态?是Blocked还是Waiting?

В это время мы должны сказать ему праведно: дорогой, это совсем не так~

3. Ждать бесконечно

Потоку в неопределенном состоянии ожидания не выделяется время выполнения процессора, если другой поток явно не пробуждает его.

Самый простой сценарий - вызватьObject.wait()метод.

public static void main(String[] args) throws Exception {

    Object object = new Object();
    new Thread(() -> {
        synchronized (object){
        try {
            object.wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }}).start();
}
-- 输出:线程状态:WAITING

В этот момент поток находится в неопределенном состоянии ожидания, если только другой поток явно не вызоветobject.notifyAll();разбудить его.

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

public static void main(String[] args) throws Exception {

    Thread mainThread = new Thread(() -> {
        Thread subThread = new Thread(() -> {
            for (;;){}
        });
        subThread.start();
        try {
            subThread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });
    mainThread.start();
    System.out.println("线程状态:"+thread.getState());
}
//输出:线程状态:WAITING

Как и выше, в основном потокеmainThreadДочерний поток вызываетсяjoin()метод, то основной поток должен дождаться завершения выполнения дочернего потока. Так что в это время основной потокmainThreadГосударство ждет бесконечно. Еще одна вещь, на самом делеjoin()Внутри метода вызов такжеObject.wait().

Наконец, мы говоримLockSupport.park()метод, который также переводит поток в неопределенное состояние ожидания. Возможно, кто-то из моих друзей не знаком с ним и не использовал его раньше.Давайте рассмотрим пример блокировки очереди.

public static void main(String[] args) throws Exception {

    ArrayBlockingQueue<Long> queue = new ArrayBlockingQueue(1);
    Thread thread = new Thread(() -> {
        while (true){
            try {
                queue.put(System.currentTimeMillis());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    });
    thread.start();
}

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

здесь,ArrayBlockingQueueДлина равна 1. Когда мы добавляем в него данные во второй раз, мы обнаруживаем, что очередь заполнена, и поток будет ждать здесь, его исходный код точно называетсяLockSupport.park().

Точно так же это тоже довольно запутанно, позвольте мне спросить вас:阻塞队列中,如果队列为空或者队列已满,这时候执行take或者put操作的时候,线程的状态是 Blocked 吗?

В настоящее время мы должны иметь в виду, что статус потока здесь по-прежнемуWAITING. Различия и связи между ними будут рассмотрены позже.

4. Подождите ограниченное время

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

Разница между этим и неопределенным ожиданием заключается в том, что здесь нет параметра тайм-аута.

Например:

object.wait(3000);
thread.join(3000);
LockSupport.parkNanos(5000000L);
Thread.sleep(1000);

Подобные операции переводят поток в состояние ожидания на неопределенный срок.TIMED_WAITING. так какThread.sleep()Должен иметь параметр времени, чтобы он не стоял в очереди на неопределенный срок.

5. Блокировка

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

Вот, давайте посмотрим наsynchronizedпример.

public static void main(String[] args) throws Exception {

    Object object = new Object();
    Thread t1 = new Thread(() -> {
        synchronized (object){
            for (;;){}
        }
    });
    t1.start();

    Thread t2 = new Thread(() -> {
        synchronized (object){
            System.out.println("获取到object锁,线程执行。");
        }
    });
    t2.start();
    System.out.println("线程状态:"+t2.getState());
}
//输出:线程状态:BLOCKED

Глядя на приведенный выше код, блокировка объекта объекта всегда удерживается потоком t1, поэтому состояние потока t2 всегда будет заблокировано.

Давайте посмотрим на другой пример блокировки:

public static void main(String[] args){

    Lock lock = new ReentrantLock();
    lock.lock();
    Thread t1 = new Thread(() -> {
        lock.lock();
        System.out.println("已获取lock锁,线程执行");
        lock.unlock();
    });
    t1.start();
    System.out.println("线程状态:"+t1.getState());
}

С приведенным выше кодом у нас естьReentrantLock, основной поток уже удерживает блокировку, а поток t1 всегда будет ждатьlock.lock();.

Итак, каково состояние потока t1 на данный момент?

На самом деле ответWAITING, то есть неопределенное состояние ожидания. что насчет этого ?

причина в том,LockИнтерфейс представляет собой блокировку, реализованную Java API, а его базовая реализация фактически представляет собой абстрактную очередь синхронизации, сокращенноAQS.

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

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        如果tryAcquire返回false,会把当前线程放入AQS阻塞队列
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

acquireQueuedМетод поместит текущий поток в очередь блокировки AQS, а затем вызоветLockSupport.park(this);Подвесьте нить.

Таким образом, это также объясняет, почемуlock.lock()Когда блокировка получена, текущее состояние потока будетWAITING.

Люди часто спрашивают,synchronized和LockРазница, в дополнение к общему ответу, в этот момент вы также можете поговорить о разнице в состоянии потока, которую, я думаю, мало кто поймет.

6. Конец

Поток, когда он выходит или завершил свое выполнение, является конечным состоянием.

public static void main(String[] args) throws Exception {
    
    Thread thread = new Thread(() -> System.out.println("线程已执行"));
    thread.start();
    Thread.sleep(1000);
    System.out.println("线程状态:"+thread.getState());
}
//输出:  线程已执行
线程状态:TERMINATED

3. Резюме

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

Нелегко быть оригинальным, это понравится приглашенным официальным лицам перед отъездом.Это будет движущей силой для автора продолжать писать~