Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
# wait() в Java — синхронизация потоков
wait() — это метод, который позволяет потоку приостановиться и отпустить монитор объекта, ожидая, пока другой поток уведомит его о готовности продолжить работу. Это один из базовых механизмов синхронизации между потоками.
Основные концепции
Метод wait() определён в классе Object, поэтому доступен всем объектам. Он работает в тесной связи с методами notify() и notifyAll().
// Синтаксис
public final void wait() throws InterruptedException
public final void wait(long timeoutMillis) throws InterruptedException
public final void wait(long timeoutMillis, int nanos) throws InterruptedException
Как это работает
synchronized (lockObject) { // 1. Захватываем монитор
while (!condition) { // 2. Проверяем условие
lockObject.wait(); // 3. Отпускаем монитор и ждём
// 5. Другой поток вызвал notify() → просыпаемся
}
// 6. Снова захватили монитор
// Выполняем работу
}
// В другом потоке:
synchronized (lockObject) { // Захватываем тот же монитор
condition = true; // Меняем условие
lockObject.notify(); // 4. Уведомляем ждущий поток
}
Ключевые моменты
1. wait() ВСЕГДА в synchronized блоке
// ❌ Неправильно — выбросит IllegalMonitorStateException
public void badWait() {
try {
object.wait(); // ERROR!
} catch (InterruptedException e) {}
}
// ✅ Правильно
public void goodWait() {
synchronized (object) {
try {
object.wait();
} catch (InterruptedException e) {}
}
}
2. wait() отпускает монитор
synchronized (object) {
// Мы владеем монитором
object.wait();
// wait() отпускает монитор и блокируется
// Другие потоки теперь могут захватить монитор
// После notify() монитор снова захватывается
}
3. Всегда проверяй условие в while, не if
// ❌ Неправильно
synchronized (lock) {
if (!isReady) {
lock.wait(); // Может вернуться раньше, чем изменится isReady
}
// Используем isReady
}
// ✅ Правильно
synchronized (lock) {
while (!isReady) { // Перепроверяем условие после пробуждения
lock.wait();
}
// Используем isReady
}
Практический пример: Producer-Consumer
public class ProducerConsumer {
private final Queue<Integer> buffer = new LinkedList<>();
private final int capacity = 5;
private final Object lock = new Object();
// ПРОИЗВОДИТЕЛЬ
public void produce(Integer value) throws InterruptedException {
synchronized (lock) {
// Ждём, пока буфер освободится
while (buffer.size() == capacity) {
System.out.println("[Producer] Буфер полный, жду...");
lock.wait(); // Отпускаем монитор и ждём
}
buffer.add(value);
System.out.println("[Producer] Добавил " + value + ", в буфере: " + buffer.size());
// Уведомляем консумера, что данные готовы
lock.notifyAll();
}
}
// ПОТРЕБИТЕЛЬ
public Integer consume() throws InterruptedException {
synchronized (lock) {
// Ждём, пока буфер не заполнится
while (buffer.isEmpty()) {
System.out.println("[Consumer] Буфер пуст, жду...");
lock.wait(); // Отпускаем монитор и ждём
}
Integer value = buffer.poll();
System.out.println("[Consumer] Получил " + value + ", в буфере: " + buffer.size());
// Уведомляем производителя, что место освободилось
lock.notifyAll();
return value;
}
}
}
// Тестирование
public class Main {
public static void main(String[] args) {
ProducerConsumer pc = new ProducerConsumer();
// Производитель в отдельном потоке
Thread producer = new Thread(() -> {
try {
for (int i = 1; i <= 10; i++) {
pc.produce(i);
Thread.sleep(100);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
// Потребитель в отдельном потоке
Thread consumer = new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
pc.consume();
Thread.sleep(200);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
producer.start();
consumer.start();
}
}
Три варианта wait()
1. wait() — бесконечное ожидание
synchronized (lock) {
while (!condition) {
lock.wait(); // Будет ждать, пока не вызовут notify()
}
}
2. wait(long timeout) — ожидание с таймаутом
synchronized (lock) {
while (!condition) {
lock.wait(5000); // Ждёт максимум 5 секунд
// Если за 5 сек никто не вызовет notify() —
// поток пробудится сам
if (!condition) {
System.out.println("Timeout! Условие не стало true");
}
}
}
3. wait(long timeoutMillis, int nanos) — точный таймаут
lock.wait(5000, 500000); // 5 сек + 500000 наносекунд
notify() vs notifyAll()
// ❌ Использование notify() — опасно!
synchronized (lock) {
data.add(item);
lock.notify(); // Может пробудить неправильный поток
}
// ✅ Использование notifyAll() — безопаснее!
synchronized (lock) {
data.add(item);
lock.notifyAll(); // Пробуждает ВСЕ ждущие потоки
}
Почему notifyAll() лучше:
- notify() пробуждает только ОДИН случайный поток
- Он может быть не тем, который ждёт условие
- notifyAll() гарантирует, что нужный поток проснётся
- Все потоки перепроверяют условие и если оно false — ждут дальше
Spurious Wakeups — ложные пробуждения
// Поток может проснуться БЕЗ notify()!
// Поэтому ВСЕГДА используй while, не if
synchronized (lock) {
// ❌ if (поле может быть ложное пробуждение):
while (!condition) { // ✅ while — перепроверяет условие
lock.wait();
}
}
Современные альтернативы
Для новых проектов используй высокоуровневые инструменты вместо wait()/notify():
// ❌ Старый подход
Object lock = new Object();
synchronized (lock) {
lock.wait();
}
// ✅ Современный подход (BlockingQueue)
BlockingQueue<Integer> queue = new LinkedBlockingQueue<>();nqueue.take(); // Внутри используется wait()
// ✅ Современный подход (CountDownLatch)
CountDownLatch latch = new CountDownLatch(1);
latch.await(); // Ждёт, пока счётчик не станет 0
latch.countDown(); // Уменьшает счётчик
// ✅ Современный подход (Condition)
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
lock.lock();
try {
condition.await();
} finally {
lock.unlock();
}
Ключевые правила
- ✓ wait() всегда в synchronized блоке
- ✓ Всегда проверяй условие в while, не if
- ✓ Обрабатывай InterruptedException
- ✓ Используй notifyAll(), а не notify()
- ✓ Для новых проектов предпочитай BlockingQueue, Condition, CountDownLatch