undefined

Стратегия «wait-notify-notifyAll»

Java Multithreading
7 уровень , 7 лекция
Доступна
Стратегия «wait-notify-notifyAll» - 1

— Привет, Амиго!

Хочу основательно разобрать с тобой тему wait-notify. Методы wait-notify обеспечивают удобный механизм взаимодействия нитей. Также их можно использовать для построения сложных высокоуровневых механизмов взаимодействия нитей.

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

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

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

— А можно пример такого объекта-задания?

— Вот смотри:

Класс вычисляет факториал числа n при вызове метода run()
class Factorial implements Runnable
{
 public int n = 0;
 public long result = 1;

 public Factorial (int n)
 {
  this.n = n;
 }

 public void run()
 {
  for (int i=2;i<=n;i++)
   result*=i;
 }
}

— Пока ясно.

— Отлично. Тогда разберем, как должен выглядеть объект-очередь. Что ты можешь сказать про него?

— Он должен быть thread-safe. В него кладутся объекты-задания (таски) нитью, которая принимает их от пользователей, а забираются задания нитями-исполнителями.

— Ага. А если задания временно закончились?

— Тогда нити-исполнители должны ждать, пока они появятся.

— Верно. Тогда представь, что все это можно встроить в одну очередь. Вот смотри:

Очередь заданий, если задания нет, то нить засыпает и ждет его появления:
public class JobQueue
{
 ArrayList jobs = new ArrayList();

 public synchronized void put(Runnable job)
 {
  jobs.add(job);
  this.notifyAll();
 }

 public synchronized Runnable getJob()
 {
  while (jobs.size()==0)
   this.wait();

  return jobs.remove(0);
 }
}

У нас есть метод getJob, который смотрит, если список работы (jobs) пуст, то нить засыпает (wait), пока в списке что-то не появится.

А есть еще метод put, который позволяет добавить в список jobs новое задание (job). Как только новое задание добавлено, вызывается метод notifyAll. Вызов этого метода пробудит все нити-исполнители, которые заснули внутри метода getJob.

— А можешь напомнить еще раз, как работают методы wait и notify?

— Метод wait вызывается только внутри блока synchronized, у объекта-мютекса. В нашем случае – это this. При этом происходит две вещи:

1) Нить засыпает.

2) Нить временно освобождает мютекс (пока не проснется).

После этого другие нити могу входить в блок synchronized и занимать этот же мютекс.

Метод notifyAll тоже можно вызвать только внутри блока synchronized у объекта-мютекса. В нашем случае – это this. При этом происходит две вещи:

1) Просыпаются все нити, которые заснули на этом же объекте-мютексе.

2) Как только текущая нить выйдет из блока synchronized, одна из проснувшихся нитей захватит мютекс и продолжит свою работу. Когда она освободит мютекс, другая проснувшаяся нить захватит мютекс и т.д.

Очень похоже на автобус. Вы заходите внутрь, хотите передать за проезд, а водителя нет. И вы «засыпаете». Со временем вас набивается целый автобус, но за проезд пока никто не передает – некому. Затем заходит водитель, вы слышите « – Передаем за проезд». И тут начинается…

— Интересное сравнение. А что такое автобус?

— А это Хулио рассказывал. Были такие странные штуки в 21 веке.

Комментарии (34)
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION
Виталий Уровень 31 Минск Белоруссия
2 июля 2021
Anonymous #2631266 Уровень 41 Одесса
15 мая 2021
В Москве так. А у нас платим на выходе.
perchiikk Уровень 35
29 января 2021
из-за картинки сразу вспомнила, как в детстве "приклеилась" языком к катку :(
Art Уровень 35 Москва Россия
11 сентября 2020
https://www.youtube.com/watch?v=CA4oUmywL28
Вася Уровень 37 Новосибирск
24 августа 2020
К уже описанным косякам добавлю еще парочку: - getJob не может возвращать Runnable ибо ArrayList из Object(а нисходящее преобразование автоматически не проводится) - wait() кидает исключение, которого нет не в сигнатуре не в try/catch И есть еще важное уточнение о котором не слова в лекции. Как Вы думаете зачем там вообще цикл в методе, ведь вроде бы и без него все ок должно быть? Затем что возможны ложные срабатывание, поэтому имеется рекомендация его ставить дополнительно.
Wladyslaw Уровень 41 Украина Master
12 января 2020
Вот тут можно глянуть как оно будет с ограниченным буффером
VN Уровень 40 Россия
13 сентября 2019
Хулио там рассказывал
Andrii Gorshunov Уровень 41 Польша Expert
5 февраля 2019
Однако наверное было бы понятнее, если бы размер массива был не бесконечный, а ограниченый, например до 10 элементов. Когда 10 элементов добавились и список (ArrayList) полон, тогда добавление ждет. Если не полный то добавляет и делает notify(). В методе, который удаляет(забирает) из списка тоже цикл, что пока список пустой(нечего забирать) то ждет wait(), если не пустой, тогда забирает и уведомляет что есть место notify()
RuslanN Уровень 35 Нижний Новгород Россия
29 января 2019
непонимаю зачем в конце return jobs.remove(0);
Xytrex Уровень 41 Украина
19 августа 2018
А що таке автобус? - А це Хуліо розповідав. Були такі дивні штуки в 21 столітті.