— У цій лекції поговоримо про методи 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, але якщо вказаний час сплив, а потік ніхто не зняв з паузи – він активується сам.