← Назад к вопросам
Как поведёт себя поток, если внутри synchronized метода вызвать wait() на текущем объекте
2.0 Middle🔥 191 комментариев
#Многопоточность
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
# wait() в synchronized методе
Краткий ответ
Если вызвать wait() внутри synchronized метода:
- Поток освобождает монитор (lock) объекта
- Поток переходит в WAITING состояние
- Другие потоки могут войти в другие synchronized методы этого объекта
- Поток просыпается когда другой поток вызовет
notify()илиnotifyAll() - После пробуждения поток заново пытается получить монитор перед продолжением
Подробное объяснение
Как работает synchronized
public synchronized void myMethod() {
// Поток должен получить монитор этого объекта
// Только один поток может находиться здесь одновременно
System.out.println("Inside synchronized method");
}
// Эквивалентно:
public void myMethod() {
synchronized(this) {
// Поток должен получить монитор объекта this
System.out.println("Inside synchronized block");
}
}
Когда поток входит в synchronized блок, он захватывает монитор объекта. Пока он не выйдет из блока, другие потоки, ищущие того же монитора, будут заблокированы.
Что происходит при wait()
public synchronized void method1() {
System.out.println("Before wait");
try {
this.wait(); // ОСВОБОЖДАЕТ МОНИТОР!
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("After wait");
// Здесь поток заново захватывает монитор
}
Ключевой момент: wait() освобождает монитор текущего объекта!
Это означает:
- Монитор освобождается
- Другие потоки могут войти в synchronized методы этого объекта
- Текущий поток уходит в WAITING очередь
- Когда придёт
notify(), поток пробуждается и ЗАНОВО пытается получить монитор
Практический пример
public class Counter {
private int count = 0;
// Производитель
public synchronized void produce() {
count++;
System.out.println("Produced: " + count);
this.notifyAll(); // Разбудит ожидающие потоки
}
// Потребитель
public synchronized void consume() throws InterruptedException {
while (count == 0) {
System.out.println("Waiting for product...");
this.wait(); // ОСВОБОЖДАЕТ монитор, уходит в WAITING
// Когда пробуждается, стоит здесь
// Но сначала должен заново получить монитор!
}
count--;
System.out.println("Consumed: " + count);
}
}
Тонкости и примеры
Пример 1: Освобождение монитора
public class WaitExample {
private boolean ready = false;
public synchronized void waitForReady() throws InterruptedException {
while (!ready) {
System.out.println(Thread.currentThread().getName() + " is waiting");
this.wait(); // МОНИТОР ОСВОБОЖДАЕТСЯ ЗДЕСЬ!
// Другие потоки могут войти в setReady() правo сейчас
System.out.println(Thread.currentThread().getName() + " woke up");
}
}
public synchronized void setReady() {
System.out.println(Thread.currentThread().getName() + " setting ready");
this.ready = true;
this.notifyAll(); // Разбудит ожидающие потоки
}
}
// Демонстрация
public static void main(String[] args) throws InterruptedException {
WaitExample ex = new WaitExample();
Thread waiter = new Thread(() -> {
try {
ex.waitForReady();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "Waiter");
Thread setter = new Thread(() -> {
try {
Thread.sleep(1000);
ex.setReady();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "Setter");
waiter.start();
setter.start();
waiter.join();
setter.join();
}
// Вывод:
// Waiter is waiting <- Waiter вошёл в synchronized, монитор занят
// (монитор ОСВОБОЖДАЕТСЯ после wait())
// Setter setting ready <- Setter может войти, потому что монитор свободен
// Waiter woke up <- Waiter просыпается и заново захватывает монитор
Пример 2: Spurious wakeups (ложные пробуждения)
// НЕПРАВИЛЬНО (опасно от spurious wakeups)
public synchronized void consume() throws InterruptedException {
if (count == 0) { // ЕСЛИ, а не ПОКА!
this.wait();
}
count--; // Может быть неправильно, если проснулись без notify
}
// ПРАВИЛЬНО (защита от spurious wakeups)
public synchronized void consume() throws InterruptedException {
while (count == 0) { // ПОКА, а не ЕСЛИ
this.wait();
}
count--; // Гарантированно count > 0
}
Почему while, а не if? Потому что после пробуждения нужно заново проверить условие:
- Может быть несколько потребителей
- Один поток может разбудиться, но другой уже забрал count
- Или произойдёт spurious wakeup (в некоторых системах)
Пример 3: Состояния потока
public synchronized void demonstrateStates() throws InterruptedException {
System.out.println("RUNNABLE: поток выполняется внутри synchronized");
this.wait();
// WAITING: поток здесь, монитор ОСВОБОЖДЁН
// Другие потоки могут войти в synchronized методы
System.out.println("BLOCKED: пытается заново захватить монитор");
// После просыпания поток переходит в BLOCKED
// Он ждёт, пока других потоков выйдут из synchronized
System.out.println("RUNNABLE: заново вошёл в synchronized");
}
Частые ошибки
Ошибка 1: Забыл try-catch
// ОШИБКА: wait() выбрасывает InterruptedException!
public synchronized void wrongWait() {
this.wait(); // Ошибка компиляции
}
// Правильно
public synchronized void correctWait() throws InterruptedException {
this.wait();
}
// Или с try-catch
public synchronized void correctWait2() {
try {
this.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
Ошибка 2: wait() на объекте, который не владеешь
// ОШИБКА: выбросит IllegalMonitorStateException
String str = "hello";
synchronized(str) {
str.wait(); // Может не работать, StringPool оптимизация
}
// ОШИБКА: монитор не захвачен
public void badWait() {
try {
this.wait(); // IllegalMonitorStateException!
} catch (InterruptedException e) {}
}
Ошибка 3: notify() вместо notifyAll()
// Потенциально опасно
public synchronized void release() {
this.ready = true;
this.notify(); // Разбудит только ОДИН поток
// А если их несколько? Другие останутся спать!
}
// Безопаснее
public synchronized void release() {
this.ready = true;
this.notifyAll(); // Разбудит ВСЕ потоки
// Они проверят условие while и либо выйдут, либо уснут снова
}
Современный подход (java.util.concurrent)
Вместо wait/notify лучше использовать современные инструменты:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class ModernWaitExample {
private final ReentrantLock lock = new ReentrantLock();
private final Condition ready = lock.newCondition();
private boolean isReady = false;
public void waitForReady() throws InterruptedException {
lock.lock();
try {
while (!isReady) {
ready.await(); // Эквивалент wait()
}
} finally {
lock.unlock();
}
}
public void setReady() {
lock.lock();
try {
isReady = true;
ready.signalAll(); // Эквивалент notifyAll()
} finally {
lock.unlock();
}
}
}
Заключение
Главное понимание: wait() ОСВОБОЖДАЕТ монитор текущего объекта, позволяя другим потокам войти в synchronized методы того же объекта. Это критично для межпоточной синхронизации. Всегда используй while для проверки условия, не if, и помни о possibilty spurious wakeups.