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

Что такое notify?

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

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

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

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

Что такое notify?

notify() — это метод в Java для синхронизации потоков. Это один из самых старых (и сложных) механизмов многопоточности, основанный на Monitor pattern.

Основы: wait() и notify()

В Java есть три связанных метода, определённых в Object класса:

public final void wait()              // Ждать, пока другой поток вызовет notify
public final void notify()            // Разбудить один ожидающий поток
public final void notifyAll()         // Разбудить все ожидающие потоки

Ключевое правило: эти методы ВСЕГДА должны быть вызваны внутри synchronized блока:

synchronized(lock) {
    while (!condition) {
        lock.wait();  // Отпускаем lock и ждем
    }
    // Выполняем работу
}

// В другом потоке:
synchronized(lock) {
    // Меняем состояние
    condition = true;
    lock.notify();  // Разбуждаем поток
}

Как работает notify()?

Когда поток вызывает wait():

  1. Освобождает монитор (synchronized lock)
  2. Засыпает (переходит в WAITING состояние)
  3. Другой поток может захватить тот же монитор
  4. Когда другой поток вызывает notify(), ждущий поток просыпается
  5. Но он НЕ тут же выполняется! Ему сначала нужно заново захватить монитор
Поток 1                          Поток 2
─────────────────────────────    ─────────────────────────────
synchronized(lock) {             synchronized(lock) {
    wait()                            condition = true
    ↓                                 notify()  ← просыпаешься
    (WAITING, без lock)          }
    ↓                            
    просыпается (требует lock)       lock свободен
    захватывает lock             
    продолжает выполнение        
}

Практический пример: Producer-Consumer

public class ProducerConsumer {
    private int buffer = 0;
    private boolean empty = true;
    private final Object lock = new Object();

    // Producer: производит данные
    public void produce(int value) throws InterruptedException {
        synchronized(lock) {
            while (!empty) {
                // Буфер полон, жди пока Consumer заберет
                lock.wait();
            }
            
            buffer = value;
            empty = false;
            System.out.println("Produced: " + value);
            
            // Разбуди Consumer
            lock.notify();
        }
    }

    // Consumer: потребляет данные
    public int consume() throws InterruptedException {
        synchronized(lock) {
            while (empty) {
                // Буфер пустой, жди Producer
                lock.wait();
            }
            
            int value = buffer;
            empty = true;
            System.out.println("Consumed: " + value);
            
            // Разбуди Producer
            lock.notify();
            return value;
        }
    }
}

Использование:

ProducerConsumer pc = new ProducerConsumer();

Thread producer = new Thread(() -> {
    try {
        for (int i = 1; i <= 5; i++) {
            pc.produce(i);
            Thread.sleep(100);
        }
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
});

Thread consumer = new Thread(() -> {
    try {
        for (int i = 1; i <= 5; i++) {
            pc.consume();
            Thread.sleep(200);
        }
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
});

producer.start();
consumer.start();

Вывод:

Produced: 1
Consumed: 1
Produced: 2
Consumed: 2
Produced: 3
Consumed: 3
// ...

notify() vs notifyAll()

notify() — разбуждает ОДИН ждущий поток (непредсказуемо какой):

Object lock = new Object();

// Несколько потоков ждут
Thread t1 = new Thread(() -> {
    synchronized(lock) {
        lock.wait();  // WAITING
        System.out.println("T1 пробудился");
    }
});

Thread t2 = new Thread(() -> {
    synchronized(lock) {
        lock.wait();  // WAITING
        System.out.println("T2 пробудился");
    }
});

Thread t3 = new Thread(() -> {
    synchronized(lock) {
        lock.notify();  // Разбудит ТОЛЬКО одного (T1 или T2)
        System.out.println("T3 разбудил одного");
    }
});

t1.start();
t2.start();
Thread.sleep(100);
t3.start();

notifyAll() — разбуждает ВСЕ ждущие потоки:

Thread t3 = new Thread(() -> {
    synchronized(lock) {
        lock.notifyAll();  // Разбудит и T1, и T2
        System.out.println("T3 разбудил всех");
    }
});

Важное правило: используй while, не if

// НЕПРАВИЛЬНО - может привести к spurious wakeup
synchronized(lock) {
    if (!condition) {
        lock.wait();
    }
    // Может произойти spurious wakeup (пробуждение без notify)
    // и condition может быть false!
}

// ПРАВИЛЬНО - проверяем условие после пробуждения
synchronized(lock) {
    while (!condition) {
        lock.wait();
    }
    // Гарантированно condition == true
}

Пример с несколькими потребителями

public class ThreadSafeQueue {
    private Queue<Integer> queue = new LinkedList<>();
    private static final int MAX_SIZE = 5;
    private final Object lock = new Object();

    public void produce(int value) throws InterruptedException {
        synchronized(lock) {
            while (queue.size() >= MAX_SIZE) {
                lock.wait();  // Жди, пока есть место
            }
            queue.add(value);
            System.out.println("Produced: " + value);
            lock.notifyAll();  // Разбуди всех
        }
    }

    public int consume() throws InterruptedException {
        synchronized(lock) {
            while (queue.isEmpty()) {
                lock.wait();  // Жди данные
            }
            int value = queue.poll();
            System.out.println("Consumed: " + value);
            lock.notifyAll();  // Разбуди всех
            return value;
        }
    }
}

Проблемы с notify()

1. Spurious wakeup

Поток может пробудиться БЕЗ вызова notify():

synchronized(lock) {
    while (!condition) {  // ВСЕГДА используй while!
        lock.wait();  // Может пробудиться спонтанно
    }
}

2. Lost wakeup

Если notify() вызван раньше wait(), сигнал потеряется:

Thread t1 = new Thread(() -> {
    synchronized(lock) {
        Thread.sleep(100);  // Задержка
        lock.wait();  // Поток t2 уже вызвал notify()!
        System.out.println("Никогда не будет выполнено");  // DEADLOCK
    }
});

Thread t2 = new Thread(() -> {
    synchronized(lock) {
        lock.notify();  // Разбуждает никого
    }
});

t2.start();
t1.start();

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

В Java 5+ есть лучшие способы синхронизации:

// Вместо notify - используй Condition
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();

lock.lock();
try {
    while (!condition) {
        condition.await();  // Более удобно чем wait()
    }
    condition.signalAll();  // Более удобно чем notifyAll()
} finally {
    lock.unlock();
}

// Или используй BlockingQueue
BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(5);
queue.put(value);  // Блокируется если полная
int value = queue.take();  // Блокируется если пустая

Когда использовать notify()

  • Очень редко в современном коде
  • Только для старого кода или очень специфичных случаев
  • Вместо этого используй:
    • BlockingQueue
    • Condition variables
    • CountDownLatch
    • CyclicBarrier
    • Semaphore

Заключение

notify():

  • Пробуждает один ждущий поток
  • Требует synchronized
  • Используется с wait() для синхронизации
  • ВАЖНО: всегда используй while для проверки условия
  • ВАЖНО: используй notifyAll() вместо notify() обычно
  • В современном коде предпочитай Condition или BlockingQueue
  • Очень легко сделать ошибку (deadlock, spurious wakeup, lost wakeup)
Что такое notify? | PrepBro