← Назад к вопросам

Как поведёт себя поток, если внутри synchronized метода вызвать wait() на текущем объекте

2.0 Middle🔥 191 комментариев
#Многопоточность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

# wait() в synchronized методе

Краткий ответ

Если вызвать wait() внутри synchronized метода:

  1. Поток освобождает монитор (lock) объекта
  2. Поток переходит в WAITING состояние
  3. Другие потоки могут войти в другие synchronized методы этого объекта
  4. Поток просыпается когда другой поток вызовет notify() или notifyAll()
  5. После пробуждения поток заново пытается получить монитор перед продолжением

Подробное объяснение

Как работает 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.