Ответ
Методы wait(), notify() и notifyAll() определены в классе Object и используются для межпоточной синхронизации (cooperative threading). Они должны вызываться внутри синхронизированного блока или метода.
Принцип работы:
- Поток захватывает монитор объекта (через
synchronized). - Вызов
wait()освобождает монитор и переводит текущий поток в состояние ожидания. - Другой поток, захватив тот же монитор, вызывает
notify()(разбудить один случайный поток) илиnotifyAll()(разбудить все потоки). - Пробужденный поток пытается перезахватить монитор и продолжает выполнение после вызова
wait().
Пример шаблона "Producer-Consumer":
class SharedBuffer {
private final Queue<Integer> queue = new LinkedList<>();
private final int capacity;
public SharedBuffer(int capacity) { this.capacity = capacity; }
// Метод производителя
public synchronized void produce(int value) throws InterruptedException {
while (queue.size() == capacity) { // Всегда используйте цикл для проверки условия!
wait(); // Ждет, пока потребитель не освободит место
}
queue.add(value);
System.out.println("Produced: " + value);
notifyAll(); // Уведомляет ожидающих потребителей (и производителей)
}
// Метод потребителя
public synchronized int consume() throws InterruptedException {
while (queue.isEmpty()) { // Проверка в цикле на spurious wakeup
wait(); // Ждет, пока производитель не добавит данные
}
int value = queue.poll();
System.out.println("Consumed: " + value);
notifyAll(); // Уведомляет ожидающих производителей
return value;
}
}
Ключевые правила:
- Всегда вызывайте эти методы внутри
synchronizedблока, владея монитором того же объекта. - Всегда проверяйте условие ожидания в цикле
while, а не вif. Это защищает от ложных пробуждений (spurious wakeups). - Предпочитайте
notifyAll()для простоты, если нет строгих требований к производительности. - В современном коде часто лучше использовать высокоуровневые средства из
java.util.concurrent(например,BlockingQueue,LocksсCondition).
Ответ 18+ 🔞
Да ты посмотри, какие древние заклинания всплыли! Прямо как дедовский goto, только для потоков. Ну ладно, разберём эту магию, пока кто-нибудь не начал её в продакшене использовать, не разобравшись.
Вот есть у тебя класс Object, прародитель всего сущего в Java. И зачем-то ему понадобилось запихнуть туда три метода: wait(), notify() и notifyAll(). Это как если бы в фундамент дома вмуровали пару кирпичей для будущей пристройки, о которой ещё не думали. Идея была в том, чтобы потоки могли друг другу сигнальчики подавать: «Я тут посплю, разбуди, как что-то появится».
Как это, блядь, работает, если по-простому:
- Поток должен захватить монитор объекта. Это как ключ от сортира. Без ключа — нихуя не сделаешь. Ключ получаешь, зайдя в
synchronizedблок или метод. - Вызвав
wait(), поток этот самый ключ отпускает нахуй и засыпает в предбаннике, в состоянии ожидания. - Другой поток, подхватив ключ (войдя в синхронизированный блок с тем же объектом), может крикнуть
notify()(будит одного случайного соню) илиnotifyAll()(будит всех, кто ждёт у этого объекта). - Пробуждённый поток не сразу продолжает работу, а сначала снова пытается урвать этот ебучий ключ-монитор. Получилось — выполнение идёт дальше с того места, где был
wait().
Вот тебе классика, шаблон «Производитель-Потребитель», на котором поколения студентов мозги ломали:
class SharedBuffer {
private final Queue<Integer> queue = new LinkedList<>();
private final int capacity;
public SharedBuffer(int capacity) { this.capacity = capacity; }
// Метод производителя
public synchronized void produce(int value) throws InterruptedException {
while (queue.size() == capacity) { // ВНИМАНИЕ! ЦИКЛ, БЛЯДЬ, А НЕ IF!
wait(); // Ждёт, пока потребитель не освободит место
}
queue.add(value);
System.out.println("Produced: " + value);
notifyAll(); // Орёт на всех, кто спит: "Просыпайтесь, мудаки!"
}
// Метод потребителя
public synchronized int consume() throws InterruptedException {
while (queue.isEmpty()) { // Опять цикл! На spurious wakeup проверка!
wait(); // Ждёт, пока производитель не добавит данные
}
int value = queue.poll();
System.out.println("Consumed: " + value);
notifyAll(); // Орёт производителям, что место освободилось
return value;
}
}
Главные правила, нарушив которые, получишь race condition и геморрой:
- Ключ обязателен. Вызывай
wait/notifyтолько внутриsynchronizedблока, владея монитором того же самого объекта. ИначеIllegalMonitorStateExceptionприлетит — как пощёчина. - Проверка в цикле
while, ёпта! Никогда не проверяй условие черезif. Поток может проснуться просто так, от балды (spurious wakeup), даже если его никто не будил. Цикл гарантирует, что мы проверим условие заново и, если всё ещё не готово, снова уснём. Это святое правило, блядь. notifyAll()проще и безопаснее.notify()будит кого-то одного, и если разбудил не того, кто нужен (например, другого производителя, когда очередь полная), все могут встать в deadlock.notifyAll()— это как «подъём!» в армии, проснутся все, но работать продолжит только тот, для кого условие выполнилось.- Это старьё. Серьёзно. Для новых проектов смотри в сторону
java.util.concurrent. ТамBlockingQueue,LocksсCondition— это как перейти с дубины на штурмовую винтовку. Меньше шансов себе в ногу выстрелить.
Короче, знать это надо, как историю Древнего Рима, чтобы понимать легаси-код. Но самому такое писать в 21 веке — это уже, прости, мартышлюшка какая-то.