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

Можно ли вызвать метод notify у Object вне блока synchronized?

3.0 Senior🔥 111 комментариев
#Многопоточность#Основы Java

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

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

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

Можно ли вызвать notify вне synchronized блока

Ответ: Технически можно, но это ОЧЕНЬ ПЛОХАЯ ИДЕЯ! Это вызовет IllegalMonitorStateException в runtime. Вот полное объяснение:

1. Быстрый ответ

Попытка вызвать notify вне synchronized

public class BadNotifyExample {
    
    public static void main(String[] args) {
        Object lock = new Object();
        
        // ✗ ПЛОХО: вызов notify вне synchronized
        lock.notify();  // IllegalMonitorStateException!
        
        // ✓ ПРАВИЛЬНО: notify внутри synchronized
        synchronized (lock) {
            lock.notify();
        }
    }
}

/*
Результат:
exception in thread "main" java.lang.IllegalMonitorStateException
    at java.lang.Object.notify(Native Method)
    at BadNotifyExample.main(BadNotifyExample.java:9)
*/

2. Почему это так работает

Синхронизация требует владения монитором (monitor)

public class MonitorOwnershipExplanation {
    
    public void explainConcept() {
        /*
        В Java объект имеет встроенный "monitor" (лок).
        
        Методы wait(), notify(), notifyAll() работают с этим монитором:
        - Они могут вызываться ТОЛЬКО если текущий поток владеет монитором
        - Владеть монитором = находиться в synchronized блоке/методе
        
        Структура:
        synchronized (object) {
            // Текущий поток владеет монитором object
            object.wait();      // ✓ OK
            object.notify();    // ✓ OK
            object.notifyAll(); // ✓ OK
        }
        
        object.notify(); // ✗ ОШИБКА!
        // Текущий поток не владеет монитором
        */
    }
}

3. Полная иерархия wait/notify

Как правильно использовать notify

public class ProducerConsumerPattern {
    
    private final Queue<Integer> buffer = new LinkedList<>();
    private final int MAX_SIZE = 10;
    
    // PRODUCER
    public void produce(int value) throws InterruptedException {
        synchronized (buffer) {  // ← ОБЯЗАТЕЛЬНО!
            
            // Ждем, если буфер полный
            while (buffer.size() >= MAX_SIZE) {
                buffer.wait();  // ✓ OK (we own the monitor)
            }
            
            // Добавляем элемент
            buffer.add(value);
            System.out.println("Produced: " + value);
            
            // Уведомляем потребителей
            buffer.notifyAll();  // ✓ OK (we own the monitor)
        }
    }
    
    // CONSUMER
    public int consume() throws InterruptedException {
        synchronized (buffer) {  // ← ОБЯЗАТЕЛЬНО!
            
            // Ждем, если буфер пустой
            while (buffer.isEmpty()) {
                buffer.wait();  // ✓ OK
            }
            
            // Берем элемент
            int value = buffer.poll();
            System.out.println("Consumed: " + value);
            
            // Уведомляем производителей
            buffer.notifyAll();  // ✓ OK
            
            return value;
        }
    }
}

4. Ошибки и их причины

Ошибка 1: Вызов вне synchronized

public class Error1_OutsideSynchronized {
    
    public void wrong() {
        Object lock = new Object();
        
        lock.wait();     // ✗ IllegalMonitorStateException
        lock.notify();   // ✗ IllegalMonitorStateException
        lock.notifyAll(); // ✗ IllegalMonitorStateException
    }
    
    public void correct() throws InterruptedException {
        Object lock = new Object();
        
        synchronized (lock) {
            lock.wait();      // ✓ OK
            lock.notify();    // ✓ OK
            lock.notifyAll(); // ✓ OK
        }
    }
}

Ошибка 2: Вызов в методе, но не в synchronized методе

public class Error2_MethodNotSynchronized {
    
    // ✗ ОШИБКА: synchronized класс, но не метод
    public void badMethod() {
        this.notify();  // IllegalMonitorStateException!
        // Нужно synchronized(this) или synchronized метод
    }
    
    // ✓ ПРАВИЛЬНО: synchronized метод
    public synchronized void goodMethod() {
        this.notify();  // ✓ OK
    }
    
    // Эквивалент
    public void goodMethodExplicit() {
        synchronized (this) {
            this.notify();  // ✓ OK
        }
    }
}

Ошибка 3: Вызов с неправильным объектом

public class Error3_WrongObject {
    
    public void wrong() {
        Object lock1 = new Object();
        Object lock2 = new Object();
        
        synchronized (lock1) {
            lock2.notify();  // ✗ IllegalMonitorStateException!
            // Владеем lock1, но пытаемся notify lock2
        }
    }
    
    public void correct() {
        Object lock = new Object();
        
        synchronized (lock) {
            lock.notify();  // ✓ OK
            // Владеем lock и notify на lock
        }
    }
}

5. Реальные сценарии использования

Сценарий 1: Wait-Notify паттерн

public class WaitNotifyPattern {
    
    private boolean condition = false;
    private final Object lock = new Object();
    
    // Поток 1: Ждет условие
    public void waitForCondition() throws InterruptedException {
        synchronized (lock) {
            while (!condition) {
                System.out.println("Waiting...");
                lock.wait();  // Отпускает монитор и ждет
                // Когда notify → просыпается и реаттачит монитор
            }
            System.out.println("Condition met!");
        }
    }
    
    // Поток 2: Устанавливает условие и уведомляет
    public void setConditionAndNotify() {
        synchronized (lock) {
            condition = true;
            System.out.println("Condition set!");
            lock.notifyAll();  // Пробуждает ждущие потоки
        }
    }
}

// Использование
public class WaitNotifyDemo {
    public static void main(String[] args) {
        WaitNotifyPattern wnp = new WaitNotifyPattern();
        
        // Поток 1: Ждет
        Thread t1 = new Thread(() -> {
            try {
                wnp.waitForCondition();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        
        // Поток 2: Устанавливает условие через 2 секунды
        Thread t2 = new Thread(() -> {
            try {
                Thread.sleep(2000);
                wnp.setConditionAndNotify();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        
        t1.start();
        t2.start();
    }
}

Сценарий 2: Pipeline обработки

public class DataPipeline {
    
    private Integer data = null;
    private final Object lock = new Object();
    
    // Producer: создает данные
    public void produce(int value) {
        synchronized (lock) {
            while (data != null) {
                try {
                    lock.wait();  // Жди, пока consumer обработает
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
            
            data = value;
            System.out.println("Produced: " + value);
            lock.notifyAll();  // Пробуди consumer
        }
    }
    
    // Consumer: обрабатывает данные
    public Integer consume() {
        synchronized (lock) {
            while (data == null) {
                try {
                    lock.wait();  // Жди, пока producer создаст
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
            
            int value = data;
            data = null;  // Очистили
            System.out.println("Consumed: " + value);
            lock.notifyAll();  // Пробуди producer
            return value;
        }
    }
}

6. Правила для wait/notify

Правило 1: Всегда в synchronized блоке

// ✗ ПЛОХО
object.wait();
object.notify();
object.notifyAll();

// ✓ ПРАВИЛЬНО
synchronized (object) {
    object.wait();
    object.notify();
    object.notifyAll();
}

Правило 2: Использовать while, не if

// ✗ ПЛОХО: spurious wakeup может разбудить без причины
synchronized (lock) {
    if (!condition) {
        lock.wait();
    }
    // код выполняется, но condition может быть false!
}

// ✓ ПРАВИЛЬНО: проверяем в цикле
synchronized (lock) {
    while (!condition) {
        lock.wait();
    }
    // Гарантирует, что condition = true
}

Правило 3: Использовать notifyAll, не notify

// ✗ МЕНЕЕ БЕЗОПАСНО: notify() может пробудить неправильный поток
synchronized (lock) {
    lock.notify();
}

// ✓ ПРЕДПОЧТИТЕЛЬНО: notifyAll() пробуждает всех
synchronized (lock) {
    lock.notifyAll();
}

7. Современные альтернативы

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

// 1. CountDownLatch
import java.util.concurrent.*;

public class CountDownLatchExample {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(1);
        
        new Thread(() -> {
            System.out.println("Doing work...");
            latch.countDown();  // Сигнализирует завершение
        }).start();
        
        latch.await();  // Ждет без synchronized!
        System.out.println("Work completed");
    }
}

// 2. ReentrantLock с Condition
public class ReentrantLockExample {
    private final Lock lock = new ReentrantLock();
    private final Condition condition = lock.newCondition();
    
    public void waitFor() throws InterruptedException {
        lock.lock();
        try {
            condition.await();  // Вместо wait()
        } finally {
            lock.unlock();
        }
    }
    
    public void signal() {
        lock.lock();
        try {
            condition.signalAll();  // Вместо notifyAll()
        } finally {
            lock.unlock();
        }
    }
}

// 3. BlockingQueue (самый простой вариант)
public class BlockingQueueExample {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<Integer> queue = new LinkedBlockingQueue<>();
        
        new Thread(() -> {
            queue.put(42);  // Вставить
        }).start();
        
        Integer value = queue.take();  // Взять (ждет если пусто)
        System.out.println("Got: " + value);
    }
}

8. Сравнительная таблица

ИнструментRequires synchronizedThread-safeСовременный
wait/notify✓ ДА✓ Да✗ Нет (Java 1.0)
CountDownLatch✗ Нет✓ Да✓ Да (Java 1.5)
Condition✓ Да (с Lock)✓ Да✓ Да (Java 1.5)
BlockingQueue✗ Нет✓ Да✓ Да (Java 1.5)

Итоговый ответ

Нет, НЕЛЬЗЯ вызвать notify вне synchronized блока!

Почему:

IllegalMonitorStateException при попытке

Правило:

synchronized (lock) {
    lock.notify();      // ✓ OK
    lock.notifyAll();   // ✓ OK
    lock.wait();        // ✓ OK
}

lock.notify();  // ✗ IllegalMonitorStateException!

Механизм:

  • Потоки владеют "монитором" объекта только в synchronized
  • wait/notify работают с монитором
  • Требуют владения монитором текущим потоком

Правила использования:

  1. Всегда в synchronized блоке — это обязательно
  2. Используй while, не if — защита от spurious wakeup
  3. Используй notifyAll, не notify — безопаснее
  4. Рассмотри современные альтернативы — CountDownLatch, BlockingQueue

Best Practice:

  • Избегай wait/notify, если возможно
  • Используй java.util.concurrent классы
  • Если нужен wait/notify — изучи deadlock scenarios
Можно ли вызвать метод notify у Object вне блока synchronized? | PrepBro