wait и notify

Методы Object.wait(), Object.notify() и Object.notifyAll() — низкоуровневый механизм координации потоков. Они позволяют потоку приостановиться внутри синхронизированного блока, пока другой поток не сообщит, что условие, которого он ждёт, выполнено.

Все три метода объявлены в java.lang.Object и вызываются только когда поток уже владеет монитором этого объекта — то есть внутри synchronized. wait() освобождает монитор и переводит поток в состояние WAITING; notify() будит один из ожидающих потоков; notifyAll() будит все потоки, ожидающие на этом мониторе.

Синтаксис

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

// в другом потоке:
synchronized (lock) {
    // изменили состояние
    lock.notifyAll();         // или notify()
}

Пример 1. Простое ожидание сигнала

public class SimpleWait {
    private static final Object LOCK = new Object();
    private static boolean ready = false;

    public static void main(String[] args) throws InterruptedException {
        Thread waiter = new Thread(() -> {
            synchronized (LOCK) {
                while (!ready) {
                    try { LOCK.wait(); } catch (InterruptedException ignored) {}
                }
                System.out.println("получил сигнал");
            }
        });
        waiter.start();

        Thread.sleep(300);
        synchronized (LOCK) {
            ready = true;
            LOCK.notify();
            System.out.println("отправил сигнал");
        }
        waiter.join();
    }
}

Output:

отправил сигнал
получил сигнал

Пример 2. Producer-Consumer на одном объекте

public class ProducerConsumer {
    private static final Object LOCK = new Object();
    private static Integer value = null;

    public static void main(String[] args) {
        Thread producer = new Thread(() -> {
            for (int i = 1; i <= 3; i++) {
                synchronized (LOCK) {
                    while (value != null) {
                        try { LOCK.wait(); } catch (InterruptedException e) { return; }
                    }
                    value = i;
                    System.out.println("produced " + i);
                    LOCK.notifyAll();
                }
            }
        });

        Thread consumer = new Thread(() -> {
            for (int i = 1; i <= 3; i++) {
                synchronized (LOCK) {
                    while (value == null) {
                        try { LOCK.wait(); } catch (InterruptedException e) { return; }
                    }
                    System.out.println("consumed " + value);
                    value = null;
                    LOCK.notifyAll();
                }
            }
        });

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

Output:

produced 1
consumed 1
produced 2
consumed 2
produced 3
consumed 3

Пример 3. notify vs notifyAll

public class NotifyAllDemo {
    private static final Object LOCK = new Object();
    private static boolean go = false;

    public static void main(String[] args) throws InterruptedException {
        Runnable waiter = () -> {
            synchronized (LOCK) {
                while (!go) {
                    try { LOCK.wait(); } catch (InterruptedException e) { return; }
                }
                System.out.println(Thread.currentThread().getName() + " проснулся");
            }
        };

        Thread a = new Thread(waiter, "A");
        Thread b = new Thread(waiter, "B");
        Thread c = new Thread(waiter, "C");
        a.start(); b.start(); c.start();

        Thread.sleep(200);
        synchronized (LOCK) {
            go = true;
            LOCK.notifyAll();   // разбудит всех; notify() — только одного
        }
        a.join(); b.join(); c.join();
    }
}

Output (порядок может различаться):

A проснулся
B проснулся
C проснулся

Пример 4. wait с таймаутом

public class WaitTimeout {
    private static final Object LOCK = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            synchronized (LOCK) {
                long t0 = System.currentTimeMillis();
                try { LOCK.wait(300); } catch (InterruptedException ignored) {}
                long elapsed = System.currentTimeMillis() - t0;
                System.out.println("ждал около " + (elapsed / 100 * 100) + " мс");
            }
        });
        t.start();
        t.join();
    }
}

Output:

ждал около 300 мс

Пример 5. Ожидание данных датчика

public class SensorBuffer {
    private final Object lock = new Object();
    private Double last = null;

    public void publish(double v) {
        synchronized (lock) {
            last = v;
            lock.notifyAll();
        }
    }

    public double take() throws InterruptedException {
        synchronized (lock) {
            while (last == null) lock.wait();
            double v = last;
            last = null;
            return v;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SensorBuffer buf = new SensorBuffer();
        new Thread(() -> {
            try { Thread.sleep(200); buf.publish(42.5); }
            catch (InterruptedException ignored) {}
        }).start();

        System.out.println("получено: " + buf.take());
    }
}

Output:

получено: 42.5

Подводные камни

Предупреждение

wait()/notify() бросают IllegalMonitorStateException, если вызваны вне synchronized-блока на том же объекте.

Предупреждение

Всегда проверяйте условие в цикле while, а не в if — возможны spurious wakeups (поток просыпается без причины).

Совет

Для большинства практических задач предпочтительнее высокоуровневые средства из java.util.concurrent: BlockingQueue, Semaphore, CountDownLatch, Condition. См. Параллельные коллекции.

См. также

Примечание

Материал подготовлен на основе раздела «Concurrency» из Oracle Java Tutorials (docs.oracle.com/javase/tutorial/essential/concurrency/) и распространяется в соответствии с Oracle Free Documentation License. Тексты и примеры кода написаны заново на русском языке для AlashEd Wiki.