Класс Object в Java содержит три final
метода для взаимодействия потоков. Это методы wait()
, notify()
и notifyAll()
. В этой статье мы расскажем что это за методы и как их эффективно использовать в многопоточных программах.
Поток, вызывающий эти методы на любом объекте, должен иметь так называемый монитор (механизм многопоточного доступа к объекту). Если же его нет, то будет брошено исключение java.lang.IllegalMonitorStateException
.
Метод wait()
У метода wait()
есть три вариации. Один метод wait()
бесконечно ждет другой поток, пока не будет вызван метод notify()
или notifyAll()
на объекте. Другие две вариации метода wait()
ставят текущий поток в ожидание на определенное время. По истечении этого времени поток просыпается и продолжает работу.
Метод notify()
Вызов метод notify()
пробуждает только один поток, после чего этот поток начинает выполнение. Если объект ожидают несколько потоков, то метод notify()
разбудит только один из них. Выбор потока зависит от системной реализации управления потоками.
Метод notifyAll()
Метод notifyAll()
пробуждает все потоки, хотя в какой последовательности они будут пробуждаться зависит от реализации ОС.
Методы wait()
, notify()
и notifyAll()
. Практика
Давайте на примере посмотрим использование методов wait()
, notify()
и notifyAll()
.
Ниже представлен Java Bean, на котором будут работать наши потоки:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
package ua.com.prologistic; public class Message { // поле, с которым будут работать потоки через вызовы геттеров и сеттеров private String msg; public Message(String str){ this.msg=str; } public String getMsg() { return msg; } public void setMsg(String str) { this.msg=str; } } |
Для начала напишем класс, который будет ожидать другие потоки, пока они не закончат выполнение. Назовем его Waiter. В этом классе будет находиться монитор на объекте Message, используя синхронизирующий блок.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
package ua.com.prologistic; public class Waiter implements Runnable{ private Message msg; public Waiter(Message m){ this.msg = m; } @Override public void run() { String name = Thread.currentThread().getName(); synchronized (msg) { try{ System.out.println(name + " ждем вызов метода notify: " + System.currentTimeMillis()); msg.wait(); }catch(InterruptedException e){ e.printStackTrace(); } System.out.println(name + " был вызов метода notify: " + System.currentTimeMillis()); // обработаем наше сообщение System.out.println(name + " : " + msg.getMsg()); } } } |
Далее создадим класс Notifier, который будет обрабатывать объект Message, а затем вызвать метод notify, чтобы разбудить ожидающие объект Message потоки.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
package ua.com.prologistic; public class Notifier implements Runnable { private Message msg; public Notifier(Message msg) { this.msg = msg; } @Override public void run() { String name = Thread.currentThread().getName(); System.out.println(name + " стартовал"); try { Thread.sleep(1000); synchronized (msg) { msg.setMsg(name + " поток Notifier отработал"); msg.notify(); // msg.notifyAll(); } } catch (InterruptedException e) { e.printStackTrace(); } } } |
А теперь напишем тестовый класс, который будет создавать несколько потоков Waiter и Notifier.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
package ua.com.prologistic; public class WaitNotifyTest { public static void main(String[] args) { Message msg = new Message("обработать"); Waiter waiter = new Waiter(msg); new Thread(waiter,"waiter").start(); Waiter waiter1 = new Waiter(msg); new Thread(waiter1, "waiter1").start(); Notifier notifier = new Notifier(msg); new Thread(notifier, "notifier").start(); System.out.println("Стартовали все потоки"); } } |
Когда мы запустим эту программу, то в консоле увидим ход её выполнения, но она так и не закончит работу. Дело в том, что есть два потока, которые ожидают на объекте Message, а метод notify()
разбудит только один из них — другой поток все еще ждет свое уведомление.
Результат выполнения с использованием метода notify()
:
1 2 3 4 5 6 |
waiter ждем вызов метода notify: 12543137440105 waiter1 ждем вызов метода notify: 1253313744106 Стартовали все потоки notifier стартовал waiter был вызов метода notify: 1356318735011 waiter : поток Notifier отработал |
А теперь закомментируем вызов notify()
и раскомментируем вызов notifyAll()
в классе Notifier. После этого запустите программу еще раз.
Результат выполнения с использованием метода notifyAll()
:
1 2 3 4 5 6 7 8 |
waiter ждем вызов метода notify: 1276114117827 waiter1 ждем вызов метода notify: 1276114117827 Стартовали все потоки notifier стартовал waiter1 был вызов метода notify: 1276114117212 waiter1 : поток Notifier отработал waiter был вызов метода notify: 1276114117212 waiter : поток Notifier отработал |
Как видно выше, после вызова notifyAll()
все потоки отработали и программа завершила выполнение.
Следите за обновлениями раздела Многопоточность и параллелизм, и за другими статьями сайта Javadevblog.com.