Подождите / уведомите механизм уведомления для решения

Java
Подождите / уведомите механизм уведомления для решения

Подождите / уведомите механизм уведомления для решения

предисловие

Мы знаем, что механизм уведомления ожидания/уведомления Java может использоваться для реализации межпотокового взаимодействия. wait представляет собой ожидание потока.Вызов этого метода приведет к блокировке потока до тех пор, пока другой поток не вызовет метод notify или notifyAll, прежде чем он сможет продолжить выполнение. Классический шаблон производитель-потребитель реализуется с использованием механизма ожидания/уведомления. В этом посте мы углубимся в этот механизм и поймем его обоснование.

состояние потока

Прежде чем понять механизм ожидания/уведомления, ознакомьтесь с несколькими жизненными циклами потоков Java. Это начальное (NEW), работающее (RUNNABLE), блокирующее (BLOCKED), ожидающее (WAITING), ожидание по тайм-ауту (TIMED_WAITING), завершение (TERMINATED) и другие состояния (расположенные в классе перечисления java.lang.Thread.State).

Ниже приводится краткое описание этих состояний, а подробное описание находится в комментариях этой категории.

название штата инструкция
NEW Исходное состояние, поток построен, но метод start() не вызывался
RUNNABLE Состояние выполнения после вызова метода start(). В потоке java готовность и выполнение потока операционной системы в совокупности называются рабочим состоянием.
BLOCKED Состояние блокировки: поток ожидает входа в синхронизированный блок кода или метод, ожидая получения блокировки.
WAITING Состояние ожидания: поток может вызывать ожидание, соединение и другие операции, чтобы перевести себя в состояние ожидания и ждать, пока другие потоки выполнят определенные операции (например, уведомление или прерывание).
TIMED_WAITING Ожидание тайм-аута, поток вызывает сон (тайм-аут), ожидание (тайм-аут) и другие операции, чтобы войти в состояние ожидания тайм-аута, и возвращается сам по себе после тайм-аута.
TERMINATED Завершенное состояние, поток завершил выполнение

Для отношения состояния и преобразования между вышеуказанными потоками нам нужно знать

  1. И WAITING (состояние ожидания), и TIMED_WAITING (ожидание по тайм-ауту) заставят поток перейти в состояние ожидания. Разница в том, что TIMED_WAITING вернется сам по себе после тайм-аута, а WAITING нужно дождаться изменения условий.
  2. Единственным предварительным условием для перехода в состояние блокировки является ожидание получения блокировки синхронизации. Комментарии java ясно показывают, что есть только две ситуации, в которых поток может войти в состояние блокировки: одна ожидает входа в синхронизированный блок или метод, а другая — повторный вход в синхронизированный блок или метод после вызова ожидания. () метод. Это будет подробно объяснено ниже.
  3. Реализация класса Lock для блокировки не приведет к переходу потока в состояние блокировки.Нижний уровень блокировки вызывает метод LockSupport.park(), так что поток переходит в состояние ожидания.

вариант использования ожидания/уведомления

Давайте сначала разберем пример

Метод wait() может перевести поток в состояние ожидания, а метод notify() может разбудить его. Такой механизм синхронизации очень подходит для модели производитель-потребитель: потребитель потребляет ресурс, а производитель производит ресурс. Когда ресурс отсутствует, потребитель вызывает метод wait(), чтобы заблокировать себя, ожидая, пока производитель произведет; после того, как производитель завершит производство, вызовите notify/notifyAll(), чтобы разбудить потребителя для потребления.

Ниже приведен пример кода, где флаг флага указывает на наличие или отсутствие ресурсов.


public class ThreadTest {

    static final Object obj = new Object();  //对象锁

    private static boolean flag = false;

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

        Thread consume = new Thread(new Consume(), "Consume");
        Thread produce = new Thread(new Produce(), "Produce");
        consume.start();
        Thread.sleep(1000);
        produce.start();

        try {
            produce.join();
            consume.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    // 生产者线程
    static class Produce implements Runnable {

        @Override
        public void run() {

            synchronized (obj) {
                System.out.println("进入生产者线程");
                System.out.println("生产");
                try {
                    TimeUnit.MILLISECONDS.sleep(2000);  //模拟生产过程
                    flag = true;
                    obj.notify();  //通知消费者
                    TimeUnit.MILLISECONDS.sleep(1000);  //模拟其他耗时操作
                    System.out.println("退出生产者线程");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    //消费者线程
    static class Consume implements Runnable {

        @Override
        public void run() {
            synchronized (obj) {
                System.out.println("进入消费者线程");
                System.out.println("wait flag 1:" + flag);
                while (!flag) {  //判断条件是否满足,若不满足则等待
                    try {
                        System.out.println("还没生产,进入等待");
                        obj.wait();
                        System.out.println("结束等待");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("wait flag 2:" + flag);
                System.out.println("消费");
                System.out.println("退出消费者线程");
            }

        }
    }
}

Результат:

Войдите в потребительскую нить

wait flag 1:false

Еще не произведено, входит и ждет

Войдите в поток производителя

Производство

Выйдите из потока производителя

конец ожидания

wait flag 2:true

Потребление

Выход из потребительского потока

Поняв порядок вывода результатов, вы также понимаете основное использование ожидания/уведомления. Вот несколько вещей, которые нужно знать:

  1. Не отражено в примере, но важно,Вызов методов ожидания/уведомления должен быть в объектной блокировке (Монитор) в, то есть при вызове этих методов в первую очередь необходимо получить блокировку объекта.В противном случае будет выдано исключение IllegalMonitorStateException.
  2. Судя по выходным результатам, после того, как производитель вызывает notify(), потребитель не пробуждается немедленно, но не будет пробуждаться и выполняться до тех пор, пока производитель не выйдет из синхронизированного блока. (Это на самом деле легко понять, синхронизированный метод синхронизации (блок) позволяет только один поток за раз, производитель не выходит, а потребитель не может войти)
  3. Обратите внимание, что после пробуждения потребителя он выполняется из метода wait() (где он был заблокирован), а не перезапускается из синхронизированного блока.

понять глубже

В этом разделе мы исследуем взаимосвязь между ожиданием/уведомлением и состоянием потока. Получите представление о жизненном цикле потоков.

Как видно из диаграммы переходов состояний предыдущего потока, при вызове метода wait() поток войдет в WAITING (состояние ожидания), а получив уведомление() позже, он будет выполняться не сразу, а войдет в очередь блокировки, ожидая получения блокировки.

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

  1. Когда поток A (потребитель) вызывает метод ожидания (), чтобы поток A заблокировал себя в состоянии ожидания, ожидая присоединения к объектам блокировки очереди.
  2. После того, как поток B (производитель) получает блокировку, он вызывает метод уведомления, чтобы уведомить очередь ожидания об объекте блокировки, чтобы поток A попал в очередь блокировки из очереди ожидания.
  3. После того, как поток A входит в очередь блокировки, пока поток B не освободит блокировку, поток A конкурирует за блокировку и продолжает выполняться из метода wait().

использованная литература

  1. Искусство параллельного программирования на Java
  2. [Параллельное программирование на Java] Десять: Важные примечания по использованию Wait/Notify/NotifyAll для реализации межпоточной связи
  3. Параллельное программирование на Java: сотрудничество между потоками (ожидание/уведомление/засыпание/выход/присоединение)