← Назад к вопросам
На каких объектах допустимо вызывать 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 — это безопаснее и понятнее.