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

Какие знаешь альтернативы wait-notify из java.util.concurrent?

2.0 Middle🔥 171 комментариев
#ООП#Основы Java

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

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

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

Альтернативы wait-notify из java.util.concurrent

Методы wait(), notify() и notifyAll() из Object - это низкоуровневые механизмы синхронизации. Java предоставляет много более удобных альтернатив в пакете java.util.concurrent.

Проблемы с wait-notify

public class OldStyleProducer {
    private String data;
    private boolean isAvailable = false;
    
    // Производитель
    public synchronized void produce(String value) throws InterruptedException {
        while (isAvailable) {
            wait(); // Ждём, пока consumer заберёт данные
        }
        this.data = value;
        isAvailable = true;
        notifyAll(); // Уведомляем потребителей
    }
    
    // Потребитель
    public synchronized String consume() throws InterruptedException {
        while (!isAvailable) {
            wait(); // Ждём, пока производитель произведёт
        }
        String result = data;
        isAvailable = false;
        notifyAll(); // Уведомляем производителя
        return result;
    }
}

Проблемы:

  • Код запутанный и подвержен ошибкам
  • Spurious wakeups (ложные пробуждения)
  • Сложно отладить и тестировать -易 забыть notifyAll() вместо notify()

Альтернатива 1: BlockingQueue (Самая популярная)

BlockingQueue - это потокобезопасная очередь, идеальна для обмена данными между потоками.

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

public class ProducerConsumerWithQueue {
    private BlockingQueue<String> queue = new LinkedBlockingQueue<>();
    
    // Производитель
    public void produce(String value) throws InterruptedException {
        queue.put(value); // Блокируется, если очередь переполнена
    }
    
    // Потребитель
    public String consume() throws InterruptedException {
        return queue.take(); // Блокируется, если очередь пуста
    }
}

Плюсы:

  • Простая, понятная API
  • Встроенная буферизация
  • Не нужно думать о notify/wait
  • Обрабатывает spurious wakeups

Реализации:

  • LinkedBlockingQueue - неограниченная очередь на связанных списков
  • ArrayBlockingQueue - ограниченная очередь на массиве
  • SynchronousQueue - очередь размером 0 (производитель ждёт consumer)
  • PriorityBlockingQueue - очередь с приоритетами

Альтернатива 2: Condition (Lock-based синхронизация)

Condition - это более гибкая альтернатива wait-notify для работы с ReentrantLock.

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class ProducerConsumerWithCondition {
    private String data;
    private boolean isAvailable = false;
    private ReentrantLock lock = new ReentrantLock();
    private Condition dataReady = lock.newCondition();    // Условие: данные готовы
    private Condition spaceFree = lock.newCondition();    // Условие: есть место
    
    // Производитель
    public void produce(String value) throws InterruptedException {
        lock.lock();
        try {
            while (isAvailable) {
                spaceFree.await(); // Более явное, чем wait()
            }
            this.data = value;
            isAvailable = true;
            dataReady.signalAll(); // Уведомляем потребителей
        } finally {
            lock.unlock();
        }
    }
    
    // Потребитель
    public String consume() throws InterruptedException {
        lock.lock();
        try {
            while (!isAvailable) {
                dataReady.await(); // Ждём, пока данные готовы
            }
            String result = data;
            isAvailable = false;
            spaceFree.signalAll(); // Уведомляем производителя
            return result;
        } finally {
            lock.unlock();
        }
    }
}

Плюсы:

  • Более явная семантика (await вместо wait)
  • Несколько условий для одного lock
  • awaitNanos() для timeout
  • awaitUninterruptibly() для работы с InterruptedException

Когда использовать: Когда нужна тонкая настройка синхронизации с несколькими условиями.

Альтернатива 3: CountDownLatch

CountDownLatch - один поток ждёт, пока другие выполнят задачи.

import java.util.concurrent.CountDownLatch;

public class WaitForMultipleThreads {
    public static void main(String[] args) throws InterruptedException {
        int numWorkers = 3;
        CountDownLatch latch = new CountDownLatch(numWorkers);
        
        // Запускаем рабочие потоки
        for (int i = 0; i < numWorkers; i++) {
            new Thread(() -> {
                System.out.println("Worker: doing work");
                // Работаем
                latch.countDown(); // Декрементируем счётчик
            }).start();
        }
        
        // Главный поток ждёт
        System.out.println("Main: waiting for workers");
        latch.await(); // Блокируется, пока счётчик не станет 0
        System.out.println("Main: all workers done");
    }
}

Особенности:

  • Счётчик может только уменьшаться (одноразовый)
  • Все потоки ждут одного счётчика
  • Идеально для инициализации и shutdow

Альтернатива 4: CyclicBarrier

CyclicBarrier - все потоки ждут друг друга в одной точке.

import java.util.concurrent.CyclicBarrier;

public class BarrierExample {
    public static void main(String[] args) {
        int numThreads = 3;
        CyclicBarrier barrier = new CyclicBarrier(numThreads, () -> {
            System.out.println("All threads reached barrier!");
        });
        
        for (int i = 0; i < numThreads; i++) {
            final int threadId = i;
            new Thread(() -> {
                try {
                    System.out.println("Thread " + threadId + ": working");
                    Thread.sleep(1000 * threadId); // Разное время
                    System.out.println("Thread " + threadId + ": waiting at barrier");
                    barrier.await(); // Блокируется, пока не соберутся все
                    System.out.println("Thread " + threadId + ": continuing");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

Вывод:

Thread 0: working
Thread 0: waiting at barrier
Thread 1: working
Thread 1: waiting at barrier
Thread 2: working
Thread 2: waiting at barrier
All threads reached barrier!
Thread 0: continuing
Thread 1: continuing
Thread 2: continuing

Особенности:

  • Циклическая (переиспользуется)
  • Все потоки ждут друг друга
  • Может запустить action при срабатывании

Альтернатива 5: Semaphore

Semaphore - управляет доступом к ресурсам (как семафор на дороге).

import java.util.concurrent.Semaphore;

public class SemaphoreExample {
    private Semaphore semaphore = new Semaphore(2); // Максимум 2 одновременно
    
    public void accessLimitedResource() throws InterruptedException {
        semaphore.acquire(); // Берём позволение
        try {
            System.out.println(Thread.currentThread().getName() + ": accessing resource");
            Thread.sleep(1000);
        } finally {
            semaphore.release(); // Отдаём позволение
        }
    }
    
    public static void main(String[] args) {
        SemaphoreExample example = new SemaphoreExample();
        
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                try {
                    example.accessLimitedResource();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }, "Worker-" + i).start();
        }
    }
}

Плюсы:

  • Контроль количества потоков, обращающихся к ресурсу
  • Проще, чем писать самостоятельно

Альтернатива 6: Phaser

Phaser - более гибкий CountDownLatch + CyclicBarrier.

import java.util.concurrent.Phaser;

public class PhaserExample {
    public static void main(String[] args) {
        Phaser phaser = new Phaser(3); // 3 участника
        
        for (int i = 0; i < 3; i++) {
            final int threadId = i;
            new Thread(() -> {
                System.out.println("Thread " + threadId + ": phase 1");
                phaser.arriveAndAwaitAdvance(); // Ждём все потоки
                
                System.out.println("Thread " + threadId + ": phase 2");
                phaser.arriveAndAwaitAdvance(); // Ждём снова
                
                System.out.println("Thread " + threadId + ": done");
                phaser.arriveAndDeregister(); // Выходим
            }).start();
        }
    }
}

Особенности:

  • Несколько фаз (этапов)
  • Динамическое число участников
  • Регистрация/дерегистрация потоков

Альтернатива 7: CompletableFuture

CompletableFuture - асинхронная обработка с callback'ами.

import java.util.concurrent.CompletableFuture;

public class CompletableFutureExample {
    public static void main(String[] args) {
        CompletableFuture.supplyAsync(() -> {
            System.out.println("Task 1: processing");
            return "Result 1";
        }).thenAccept(result -> {
            System.out.println("Task 2: got " + result);
        }).exceptionally(e -> {
            System.out.println("Error: " + e.getMessage());
            return null;
        });
        
        // Нет блокировки главного потока!
        System.out.println("Main: continuing");
    }
}

Плюсы:

  • Полностью асинхронная
  • Callback-based
  • Обработка ошибок
  • Композиция операций

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

МеханизмКогда использоватьСложность
wait-notifyИзбегать!Высокая
BlockingQueueОбмен даннымиНизкая
ConditionСложная синхронизацияСредняя
CountDownLatchИнициализацияНизкая
CyclicBarrierСинхронизация фазНизкая
SemaphoreОграничение ресурсовНизкая
PhaserМногофазные задачиСредняя
CompletableFutureАсинхронная работаСредняя

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

  1. Избегайте wait-notify - используйте альтернативы
  2. BlockingQueue для обмена данными - самый простой случай
  3. Condition для сложной синхронизации - когда нужны различные условия
  4. CountDownLatch для инициализации - один раз и только в одну сторону
  5. CyclicBarrier для синхронизации фаз - если нужно повторять
  6. CompletableFuture для асинхронности - если не нужна блокировка

Заключение

Вместо низкоуровневых wait() и notify() в Java есть много удобных альтернатив из java.util.concurrent. Выбирайте инструмент в зависимости от задачи: BlockingQueue для обмена, Condition для сложности, CountDownLatch для инициализации и CompletableFuture для асинхронности. Код будет более надежным и понятным.