JavaRush /Курсы /Java Multithreading /Стратегия «wait-notify-notifyAll»

Стратегия «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 веке.

Комментарии (63)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
1 октября 2024
Как только текущая нить выйдет из блока synchronized а не после вызова notify() или notifyAll()
23 апреля 2025
нет)
Long_byte Уровень 43
22 июня 2024
как я понял метод

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);
 } 
потребитель
SomeBody098 Уровень 51
5 августа 2024
да, все верно )
Серж Уровень 42
9 февраля 2024
лучшее объяснение многопоточности какое я нашёл (а я долго искал) https://www.youtube.com/watch?v=zxZ0BXlTys0&list=PLlb7e2G7aSpRSBWi5jbGjIe-v_CjRO_Ug&index=10
Rolik Уровень 41
24 апреля 2023
Кидать код кусками, это практика индийских и пакистанских гуру IT. Ну и JR очевидно.
Lafaed Уровень 36
24 апреля 2023
так это одни и те же, хотя не, JR - еще хуже. индийские и пакистанские гуру - делают свою работу а не говорят заказчику скачайте код в гугле.
Topsy Барбарисов Уровень 40
12 июля 2023
вы мистер-мазохист?) вы почти на каждом уровне, если не под каждой лекцией оставляете комментарий о том, JR отстой...
Gans Electro Уровень 42
18 июля 2023
Но стоит признать что этот человек очень целеустремлен, он даже полезные коды и подсказки пишет.
Нейросеть Уровень 41
19 мая 2024
А так же хабра, стековерфлоу и всех остальных порталов. Сейчас бы фулл проект на спринге написать ради показа двух стандартных методов. А вообще думается паренёк просто байтит
Eldar K. Уровень 39 Expert
14 апреля 2023
Во-первых, мы создадим группу нитей-исполнителей, столько же, сколько и количество ядер процессора. Каждая нить сможет работать на своем ядре: нити не будут друг другу мешать, а ядра процессора не будут простаивать. А по факту Если реально имеем известное количество ядер и делаем количество нитей по количеству ядер. Вопрос (в коде Java)-можно раскидать нити, чтобы реально они работали на конкретном ядре и не залазили на другое, по сути же операционная система решает, что куда направить.
Мирослав Уровень 37 Expert
4 марта 2023
Только я вспомнил этот анекдот после текста в лекции — Метод wait вызывается только внутри блока synchronized, у объекта-мютекса. В нашем случае – это this. При этом происходит две вещи: 1) Нить засыпает. 2) Нить временно освобождает мютекс (пока не проснется). Метод notifyAll тоже можно вызвать только внутри блока synchronized у объекта-мютекса. В нашем случае – это this. При этом происходит две вещи: 1) Просыпаются все нити, которые заснули на этом же объекте-мютексе. 2) Как только текущая нить выйдет из блока synchronized, одна из проснувшихся нитей захватит мютекс и продолжит свою работу. Когда она освободит мютекс, другая проснувшаяся нить захватит мютекс и т.д.
Kurama Уровень 50
12 ноября 2022

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;
 }
}
а где проверка на положительное входящее число?
Maximmax Уровень 38
15 февраля 2023

for (int i=2;i<=n;i++) // <- здесь
   result*=i;
 }
i<=n значит что n >= i, а i = 2.
Kurama Уровень 50
20 февраля 2023
Да, а значит код цикла не отработает, result останется неизменным, и "вернётся" число 1 А факториалы для отрицательных чисел не определены...
Нейросеть Уровень 41
7 октября 2022
Лекция "Пишем свою блокирующую очередь". Мне такое прям нравится, ибо понимание работы инструмента сразу приходит на все 100%
comrade_b Уровень 39
1 июля 2022
Потокобезопасный класс ArrayBlockingQueue() из java.util.concurrent. О нем не было нормальных лекций. Если будете использовать его, то заморачиваться с cинхронизацией не надо. Прост в использовании, поскольку всю реализацию сделали уже в Java: geeksforgeeks_arrayblockingqueue
Рогов Игорь Уровень 17
17 июня 2022
"А есть еще метод put," поправьте , там метод add
Anonymous #1396846 Уровень 41
17 ноября 2022
В методе put вызывается метод add для job. Так что ошибки нет.

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