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

На каких объектах допустимо вызывать wait() внутри synchronized метода

2.7 Senior🔥 81 комментариев
#Многопоточность

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

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

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

На каких объектах вызывать wait() в synchronized методе

wait() можно вызывать ТОЛЬКО на объекте, который используется в качестве монитора (lock) для synchronized метода или блока. Это фундаментальное правило многопоточности в Java.

Основной принцип

// ✅ Правильно: вызываем wait() на объекте, который заблокирован
public synchronized void myMethod() {
    try {
        this.wait();  // OK! 'this' — это монитор для synchronized
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
}

// ❌ Неправильно: вызываем wait() без synchronized
public void myMethod() {
    try {
        this.wait();  // IllegalMonitorStateException!
        // Нет lock на 'this', поэтому нельзя вызвать wait()
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

Способ 1: wait() на 'this' в synchronized методе

public class ProducerConsumer {
    private Queue<Integer> queue = new LinkedList<>();
    private final int capacity = 10;
    
    // ✅ Правильно: synchronized метод = монитор это 'this'
    public synchronized void produce(int item) {
        // Ждём пока есть место
        while (queue.size() >= capacity) {
            try {
                this.wait(); // Вызываем на 'this' — монитор метода
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        
        queue.add(item);
        this.notifyAll(); // Пробудим потребителей
    }
    
    public synchronized int consume() {
        // Ждём пока есть элементы
        while (queue.isEmpty()) {
            try {
                this.wait(); // Вызываем на 'this' — монитор метода
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        
        int item = queue.poll();
        this.notifyAll(); // Пробудим производителей
        return item;
    }
}

Способ 2: wait() на другом объекте в synchronized блоке

public class ThreadSafeBuffer {
    
    private final Object lock = new Object(); // Отдельный монитор
    private final Queue<String> buffer = new LinkedList<>();
    
    public void putItem(String item) {
        // Синхронизируемся на 'lock', не на 'this'
        synchronized (lock) {
            while (buffer.size() >= 100) {
                try {
                    lock.wait(); // ✅ Правильно! Вызываем на 'lock'
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
            
            buffer.add(item);
            lock.notifyAll(); // Пробудим потоки ждущие на 'lock'
        }
    }
    
    public String getItem() {
        synchronized (lock) {
            while (buffer.isEmpty()) {
                try {
                    lock.wait(); // ✅ Правильно! Вызываем на 'lock'
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
            return buffer.poll();
        }
    }
}

Способ 3: НЕПРАВИЛЬНЫЕ примеры

public class WrongWaitExamples {
    
    private String data;
    private Object lock = new Object();
    
    // ❌ ОШИБКА 1: вызываем wait() на объекте который не заблокирован
    public void wrongWait1() {
        try {
            data.wait(); // IllegalMonitorStateException!
            // 'data' не заблокирована, нет synchronized на неё
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    
    // ❌ ОШИБКА 2: synchronized на 'this', но wait() на другом объекте
    public synchronized void wrongWait2() {
        try {
            lock.wait(); // ❌ IllegalMonitorStateException!
            // synchronized на 'this', но wait() на 'lock'
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    
    // ✅ ПРАВИЛЬНО: synchronized на 'lock', wait() на 'lock'
    public void correctWait() {
        synchronized (lock) {
            try {
                lock.wait(); // ✅ OK
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
}

Практический пример из IKEA

// Сценарий: Управление пулом заказов

@Service
public class OrderProcessingPool {
    
    private final Queue<Order> orderQueue = new LinkedList<>();
    private final int maxQueueSize = 1000;
    private final Object queueLock = new Object(); // Монитор для очереди
    
    private volatile boolean isRunning = true;
    
    // PRODUCER: добавляет заказы в очередь
    public void submitOrder(Order order) {
        synchronized (queueLock) { // Синхронизируемся на queueLock
            
            // Ждём если очередь переполнена
            while (orderQueue.size() >= maxQueueSize) {
                try {
                    queueLock.wait(); // ✅ wait() на том же монаторе queueLock
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    throw new RuntimeException("Order submission interrupted", e);
                }
            }
            
            orderQueue.add(order);
            logger.info("Order submitted: {}", order.getId());
            
            // Пробудим worker потоки
            queueLock.notifyAll();
        }
    }
    
    // CONSUMER: обрабатывает заказы
    public void processOrders() {
        while (isRunning) {
            Order order = null;
            
            synchronized (queueLock) { // Синхронизируемся на queueLock
                
                // Ждём если очередь пуста
                while (orderQueue.isEmpty() && isRunning) {
                    try {
                        queueLock.wait(); // ✅ wait() на том же монаторе queueLock
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                        logger.info("Worker thread interrupted");
                        return;
                    }
                }
                
                if (!orderQueue.isEmpty()) {
                    order = orderQueue.poll();
                }
                
                // Пробудим потоки ждущие в submitOrder()
                queueLock.notifyAll();
            }
            
            // Обрабатываем заказ ВНЕВЕ synchronized блока
            if (order != null) {
                processOrder(order);
            }
        }
    }
    
    public void shutdown() {
        synchronized (queueLock) {
            isRunning = false;
            queueLock.notifyAll(); // Пробудим всех ждущих
        }
    }
    
    private void processOrder(Order order) {
        // Долгая операция без блокировки очереди
        logger.info("Processing order: {}", order.getId());
        // ...
    }
}

Важные правила

Правило 1: Monitor Owner

Object lock = new Object();

synchronized (lock) {
    lock.wait();     // ✅ OK - владелец монитора
    other.wait();    // ❌ IllegalMonitorStateException - не владелец
}

Правило 2: Условные проверки в цикле

// ❌ НЕПРАВИЛЬНО: проверка БЕЗ цикла
synchronized (lock) {
    if (condition) {          // Может измениться!
        lock.wait();
    }
    // Опасно: после пробуждения условие может быть ложным
}

// ✅ ПРАВИЛЬНО: проверка В цикле
synchronized (lock) {
    while (condition) {       // Проверяем несколько раз
        lock.wait();
    }
    // После пробуждения условие гарантировано верно
}

Правило 3: Пробуждение

synchronized (lock) {
    lock.notify();     // Пробудит один поток
    // или
    lock.notifyAll();  // Пробудит всех потоков
}

Современный подход: Condition variable (Lock API)

Вместо wait/notify лучше использовать современный Lock API:

@Service
public class ModernOrderProcessingPool {
    
    private final Queue<Order> orderQueue = new LinkedList<>();
    private final ReentrantLock lock = new ReentrantLock();
    private final Condition notEmpty = lock.newCondition();
    private final Condition notFull = lock.newCondition();
    private final int capacity = 1000;
    
    public void submitOrder(Order order) {
        lock.lock();
        try {
            while (orderQueue.size() >= capacity) {
                notFull.await(); // Вместо wait()
            }
            orderQueue.add(order);
            notEmpty.signal(); // Вместо notify()
        } finally {
            lock.unlock();
        }
    }
    
    public Order getOrder() {
        lock.lock();
        try {
            while (orderQueue.isEmpty()) {
                notEmpty.await(); // Вместо wait()
            }
            Order order = orderQueue.poll();
            notFull.signal(); // Вместо notify()
            return order;
        } finally {
            lock.unlock();
        }
    }
}

Преимущества Lock API:

  • Явность (не нужно помнить какой объект монитор)
  • Несколько условных переменных
  • Лучше читается
  • Более гибкий API

Проверка: на каких объектах можно вызвать wait()

✅ Можно вызвать wait():
   1. На объекте, который используется в synchronized блоке
   2. На объекте, который используется в synchronized методе ('this')
   3. На любом объекте, если владеешь его монитором

❌ Нельзя вызвать wait():
   1. Без synchronized блока
   2. На объекте, синхронизация не на этом объекте
   3. На null

Заключение

wait() вызывается ТОЛЬКО на объекте, который заблокирован текущим потоком через synchronized.

Обычно это:

  • this в synchronized методе
  • Специфичный объект в synchronized блоке

Совет: В современном коде используй Lock и Condition вместо wait/notify — это безопаснее и понятнее.