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

Что такое notifyAll?

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

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

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

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

Что такое notifyAll

notifyAll() — это метод класса Object в Java, который пробуждает все потоки, ожидающие монитора объекта. Он используется для синхронизации между потоками.

Основные концепции

Monitor (Монитор) объекта

Каждый объект в Java имеет монитор — механизм для контроля доступа нескольких потоков:

public class SharedResource {
    private int value = 0;
    
    // Блокирует доступ к методу для других потоков
    public synchronized void setValue(int newValue) {
        this.value = newValue;
        notifyAll(); // Пробуждает все ждущие потоки
    }
    
    public synchronized int getValue() {
        while (value == 0) {
            try {
                wait(); // Текущий поток ждёт
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        return value;
    }
}

Методы синхронизации

wait() — ждать

// Потокбезопасный метод для ожидания
public synchronized void waitForValue() throws InterruptedException {
    while (value == 0) {
        wait(); // Текущий поток ждёт, пока другой поток вызовет notify()
    }
}

notify() vs notifyAll()

// notify() — пробуждает ОДИН случайный поток
public synchronized void notifyOne() {
    notify(); // Просыпается только один поток
}

// notifyAll() — пробуждает ВСЕ ждущие потоки
public synchronized void notifyAll() {
    notifyAll(); // Просыпаются все потоки
}

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

public class Buffer {
    private Queue<String> queue = new LinkedList<>();
    private final int maxSize = 10;
    
    // Производитель добавляет элементы
    public synchronized void produce(String item) throws InterruptedException {
        while (queue.size() == maxSize) {
            wait(); // Ждём, если буфер полный
        }
        queue.add(item);
        System.out.println("Добавлено: " + item);
        notifyAll(); // Пробуждаем потребителей
    }
    
    // Потребитель забирает элементы
    public synchronized String consume() throws InterruptedException {
        while (queue.isEmpty()) {
            wait(); // Ждём, если буфер пуст
        }
        String item = queue.poll();
        System.out.println("Извлечено: " + item);
        notifyAll(); // Пробуждаем производителей
        return item;
    }
}

// Использование
public class Main {
    public static void main(String[] args) {
        Buffer buffer = new Buffer();
        
        // Производитель
        new Thread(() -> {
            try {
                for (int i = 0; i < 20; i++) {
                    buffer.produce("Item " + i);
                    Thread.sleep(100);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }).start();
        
        // Потребитель
        new Thread(() -> {
            try {
                for (int i = 0; i < 20; i++) {
                    buffer.consume();
                    Thread.sleep(200);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }).start();
    }
}

Процесс работы notifyAll()

// Шаг 1: Потоки ждут
Thread t1 = new Thread(() -> {
    synchronized(lock) {
        try {
            lock.wait(); // t1 ждёт, отпускает монитор
        } catch (InterruptedException e) {}
    }
});

Thread t2 = new Thread(() -> {
    synchronized(lock) {
        try {
            lock.wait(); // t2 ждёт, отпускает монитор
        } catch (InterruptedException e) {}
    }
});

// Шаг 2: Главный поток вызывает notifyAll()
Thread main = new Thread(() -> {
    synchronized(lock) {
        lock.notifyAll(); // Пробуждает t1 и t2
        // t1 и t2 остаются в blocked state до конца этого блока
    }
    // Теперь t1 и t2 могут получить монитор
});

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

1. Множество потоков с разными условиями

public class Warehouse {
    private List<String> items = new ArrayList<>();
    
    // Разные потребители ждут разных товаров
    public synchronized String getApple() throws InterruptedException {
        while (!items.contains("apple")) {
            wait();
        }
        items.remove("apple");
        notifyAll(); // Уведомляем всех, может потребоваться другому потребителю
        return "apple";
    }
    
    public synchronized String getOrange() throws InterruptedException {
        while (!items.contains("orange")) {
            wait();
        }
        items.remove("orange");
        notifyAll(); // Уведомляем всех
        return "orange";
    }
    
    public synchronized void add(String item) {
        items.add(item);
        notifyAll(); // notifyAll() безопаснее notify()
    }
}

2. Сложные условия ожидания

public class BankAccount {
    private int balance = 0;
    
    public synchronized void withdraw(int amount) throws InterruptedException {
        // Может быть несколько потоков с разными суммами
        while (balance < amount) {
            wait(); // Ждём больше денег
        }
        balance -= amount;
        notifyAll(); // Может помочь и тем, кто делает deposit
    }
    
    public synchronized void deposit(int amount) {
        balance += amount;
        notifyAll(); // Пробуждаем всех потребителей
    }
}

wait() с timeout

public synchronized String getWithTimeout(long timeoutMs) 
    throws InterruptedException {
    long deadline = System.currentTimeMillis() + timeoutMs;
    while (queue.isEmpty()) {
        long remaining = deadline - System.currentTimeMillis();
        if (remaining <= 0) {
            return null; // Timeout
        }
        wait(remaining); // Ждём с таймаутом
    }
    return queue.poll();
}

notify() vs notifyAll() — таблица сравнения

Аспектnotify()notifyAll()
Количество пробуждённых потоков1 (случайный)Все
ПроизводительностьЛучшеЧуть хуже
БезопасностьОпасен (может случиться deadlock)Безопаснее
Условие пробужденияОДНОМНОЖЕСТВО
Spurious wakeup protectionНужнаНужна

Spurious wakeup — ложное пробуждение

// НЕПРАВИЛЬНО — может привести к ошибкам
if (condition) {
    try {
        wait();
    } catch (InterruptedException e) {}
}
process(); // Может выполниться при ложном пробуждении!

// ПРАВИЛЬНО — всегда используй while
while (!condition) {
    try {
        wait();
    } catch (InterruptedException e) {}
}
process(); // Безопасно

Современная альтернатива: Locks и Conditions

import java.util.concurrent.locks.*;

public class ModernBuffer {
    private final Lock lock = new ReentrantLock();
    private final Condition notEmpty = lock.newCondition();
    private final Condition notFull = lock.newCondition();
    private Queue<String> queue = new LinkedList<>();
    private final int maxSize = 10;
    
    public void produce(String item) throws InterruptedException {
        lock.lock();
        try {
            while (queue.size() == maxSize) {
                notFull.await(); // Более гибко, чем wait()
            }
            queue.add(item);
            notEmpty.signalAll(); // Аналог notifyAll()
        } finally {
            lock.unlock();
        }
    }
    
    public String consume() throws InterruptedException {
        lock.lock();
        try {
            while (queue.isEmpty()) {
                notEmpty.await();
            }
            String item = queue.poll();
            notFull.signalAll();
            return item;
        } finally {
            lock.unlock();
        }
    }
}

Лучшие практики

  1. Всегда используй notifyAll() — это безопаснее, чем notify()
  2. Используй while для проверки условия, не if
  3. Ловите InterruptedException — это важно для graceful shutdown
  4. Рассмотрите Locks и Conditions — они удобнее для сложной логики
  5. Избегайте wait/notify в новом коде — используй BlockingQueue, CompletableFuture и др.

Заключение

notifyAll() — это метод для пробуждения всех потоков, ожидающих монитора объекта. Он необходим для правильной синхронизации между потоками, но современные альтернативы (Locks, BlockingQueue, CompletableFuture) часто предпочтительнее для новых приложений.
Что такое notifyAll? | PrepBro