Подождите / уведомите механизм уведомления для решения
предисловие
Мы знаем, что механизм уведомления ожидания/уведомления 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 | Завершенное состояние, поток завершил выполнение |
Для отношения состояния и преобразования между вышеуказанными потоками нам нужно знать
- И WAITING (состояние ожидания), и TIMED_WAITING (ожидание по тайм-ауту) заставят поток перейти в состояние ожидания. Разница в том, что TIMED_WAITING вернется сам по себе после тайм-аута, а WAITING нужно дождаться изменения условий.
- Единственным предварительным условием для перехода в состояние блокировки является ожидание получения блокировки синхронизации. Комментарии java ясно показывают, что есть только две ситуации, в которых поток может войти в состояние блокировки: одна ожидает входа в синхронизированный блок или метод, а другая — повторный вход в синхронизированный блок или метод после вызова ожидания. () метод. Это будет подробно объяснено ниже.
- Реализация класса 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
Потребление
Выход из потребительского потока
Поняв порядок вывода результатов, вы также понимаете основное использование ожидания/уведомления. Вот несколько вещей, которые нужно знать:
- Не отражено в примере, но важно,Вызов методов ожидания/уведомления должен быть в объектной блокировке (Монитор) в, то есть при вызове этих методов в первую очередь необходимо получить блокировку объекта.В противном случае будет выдано исключение IllegalMonitorStateException.
- Судя по выходным результатам, после того, как производитель вызывает notify(), потребитель не пробуждается немедленно, но не будет пробуждаться и выполняться до тех пор, пока производитель не выйдет из синхронизированного блока. (Это на самом деле легко понять, синхронизированный метод синхронизации (блок) позволяет только один поток за раз, производитель не выходит, а потребитель не может войти)
- Обратите внимание, что после пробуждения потребителя он выполняется из метода wait() (где он был заблокирован), а не перезапускается из синхронизированного блока.
понять глубже
В этом разделе мы исследуем взаимосвязь между ожиданием/уведомлением и состоянием потока. Получите представление о жизненном цикле потоков.
Как видно из диаграммы переходов состояний предыдущего потока, при вызове метода wait() поток войдет в WAITING (состояние ожидания), а получив уведомление() позже, он будет выполняться не сразу, а войдет в очередь блокировки, ожидая получения блокировки.
Для каждого объекта есть своя очередь ожидания и очередь блокировки. Взяв в качестве примера предыдущего производителя и потребителя, мы возьмем объект obj в качестве блокировки объекта и взаимодействуем с иллюстрацией. Внутренний процесс выглядит следующим образом
- Когда поток A (потребитель) вызывает метод ожидания (), чтобы поток A заблокировал себя в состоянии ожидания, ожидая присоединения к объектам блокировки очереди.
- После того, как поток B (производитель) получает блокировку, он вызывает метод уведомления, чтобы уведомить очередь ожидания об объекте блокировки, чтобы поток A попал в очередь блокировки из очереди ожидания.
- После того, как поток A входит в очередь блокировки, пока поток B не освободит блокировку, поток A конкурирует за блокировку и продолжает выполняться из метода wait().
использованная литература
- Искусство параллельного программирования на Java
- [Параллельное программирование на Java] Десять: Важные примечания по использованию Wait/Notify/NotifyAll для реализации межпоточной связи
- Параллельное программирование на Java: сотрудничество между потоками (ожидание/уведомление/засыпание/выход/присоединение)