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

— Привет, Риша!

— Я познакомлю тебя с методами wait, notify, notifyAll класса Object.

Сегодня мы просто ознакомимся с ними, но потом еще раз вернемся и уже выделим на это больше времени.

— Хорошо.

— Эти методы были придуманы как часть механизма синхронизации нитей.

Напомню, что в Java есть встроенный механизм управления доступом к общим ресурсам (объектам) из разных нитей. Нить может объявить какой-нибудь объект занятым, и другие нити будут вынуждены ждать, пока занятый объект не освободится.

— Я помню, это делается с помощью ключевого слова synchronized.

— Правильно. Обычно такой код выглядит примерно так:

public void print()
{
 Object monitor = getMonitor();
 synchronized(monitor)
 {
  System.out.println("text");
 }
}

Помнишь, как это работает?

— Ага. Если две нити одновременно вызовут метод print(), то одна из них войдет в блок, помеченный synchronized, и заблокирует monitor, поэтому вторая нить будет ждать, пока монитор не освободится.

— Правильно. Как только нить входит в блок, помеченный synchronized, то объект-монитор помечается как занятый, и другие нити будут вынуждены ждать освобождения объекта-монитора. Один и тот же объект-монитор может использоваться в различных частях программы.

— Кстати, почему – монитор?

— Монитором принято называть объект, который хранит состояние занят/свободен.

Вот тут и вступают в дело методы wait и notify.

Собственно, методов как таковых всего два. Остальные – это лишь модификации этих методов.

Теперь разберемся, что же такое метод wait и зачем он нужен.

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

Мы же можем просто подождать, когда файл скачается. Можно просто в цикле проверять – если файл еще не скачался – спать, например, секунду и опять проверять и т.д.

Примерно так:

while(!file.isDownloaded())
{
 Thread.sleep(1000);
}
processFile(file);

Но в нашем случае такое ожидание слишком дорого. Т.к. наша нить заблокировала монитор, то другие нити вынуждены тоже ждать, хотя их данные для работы могут быть уже готовы.

Для решения этой проблемы и был придуман метод wait(). Вызов этого метода приводит к тому, что нить освобождает монитор и «становится на паузу».

Метод wait можно вызвать у объекта-монитора и только тогда, когда это монитор занят – т.е. внутри блока synchronized. При этом нить временно прекращает работу, а монитор освобождается, чтобы им могли воспользоваться другие нити.

Часто встречаются ситуации, когда в блок synchronized зашла нить, вызвала там wait, освободила монитор.

Затем туда вошла вторая нить и тоже стала на паузу, затем третья и так далее.

— А как же нить снимется с паузы?

— Для этого есть второй метод – notify.

Методы notify/notifyAll можно вызвать у объекта-монитора и только, когда этот монитор занят – т.е. внутри блока synchronized. Метод notifyAll снимает с паузы все нити, которые стали на паузу с помощью данного объекта-монитора.

Метод notify «размораживает» одну случайную нить, метод notifyAll – все «замороженные» нити данного монитора.

— Очень интересно, спасибо Риша.

— Рад, что тебе нравится, Амиго!

Есть еще модификации метода wait():

Метод wait() Пояснение
void wait(long timeout)
Нить «замерзает», но через переданное количество миллисекунд автоматически «размораживается».
void wait(long timeout, int nanos)
Нить «замерзает», но через переданное количество миллисекунд и наносекунд автоматически «размораживается».

Это, как еще говорят, wait с таймаутом. Метод работает как обычный wait, но если указанное время прошло, а нить никто не снял с паузы – она активируется сама.