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

Какие знаешь методы класса Object, связанные с многопоточностью?

2.0 Middle🔥 151 комментариев
#Многопоточность#Основы Java

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

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

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

Методы класса Object, связанные с многопоточностью

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

Основные методы Object для многопоточности

1. wait() — три перегрузки

Отпускает монитор текущего объекта и ждет, пока другой поток пробудит этот поток.

// Три варианта:
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 блока
public class WaitExample {
    private boolean dataReady = false;
    
    public synchronized void waitForData() throws InterruptedException {
        while (!dataReady) {
            this.wait();  // Отпускаем монитор и ждем
            // ВАЖНО: while, а не if! (защита от spurious wakeups)
        }
        System.out.println("Data ready!");
    }
    
    public synchronized void setDataReady() {
        dataReady = true;
        this.notifyAll();  // Пробуждаем ждущие потоки
    }
}

// С timeout
public synchronized void waitWithTimeout() throws InterruptedException {
    this.wait(5000);  // Ждем максимум 5 секунд
    // Может быть пробужден раньше если кто-то вызовет notify()
}

// С наносекундами (редко используется)
this.wait(1000, 500);  // 1000 миллисекунд + 500 наносекунд

Механизм работы wait():

  1. Проверяет, находится ли текущий поток в synchronized блоке
  2. Если нет → IllegalMonitorStateException
  3. Если да → освобождает lock
  4. Переводит поток в WAITING состояние
  5. Добавляет в wait set объекта
  6. Ждет, пока другой поток вызовет notify() или notifyAll()

2. notify() — пробудить ОДИН поток

Пробуждает произвольный поток, ждущий на этом объекте.

public final void notify()

// Пример: Producer
public class Producer implements Runnable {
    private Buffer buffer;
    
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            synchronized(buffer) {
                try {
                    while (buffer.isFull()) {
                        buffer.wait();  // Ждем, если буфер полон
                    }
                    buffer.put(i);
                    buffer.notify();  // Пробуждаем ОДНОГО потребителя
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        }
    }
}

Проблема notify():

  • Пробуждает произвольный поток (не контролируем кого)
  • Если проснулся неправильный поток → deadlock!
  • Используй notifyAll() вместо notify()

3. notifyAll() — пробудить ВСЕ потоки

Пробуждает ВСЕ потоки, ждущие на этом объекте. ПРЕДПОЧТИТЕЛЬНЕЕ notify()!

public final void notifyAll()

// Consumer
public class Consumer implements Runnable {
    private Buffer buffer;
    
    @Override
    public void run() {
        while (true) {
            synchronized(buffer) {
                try {
                    while (buffer.isEmpty()) {
                        buffer.wait();  // Ждем, если буфер пуст
                    }
                    int value = buffer.get();
                    buffer.notifyAll();  // Пробуждаем ВСЕ потоки
                    System.out.println("Got: " + value);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
        }
    }
}

Почему notifyAll() безопаснее:

  • Пробуждаются ВСЕ потоки
  • Каждый проверяет свое условие (while, не if)
  • Проснулся неправильный → вернется в wait()
  • Deadlock маловероятен

Полный пример: Producer-Consumer

public class SharedBuffer<T> {
    private Queue<T> buffer = new LinkedList<>();
    private int maxSize = 5;
    
    public synchronized void produce(T item) throws InterruptedException {
        // Ждем, пока буфер освободится
        while (buffer.size() == maxSize) {
            System.out.println(Thread.currentThread().getName() + ": Buffer full, waiting...");
            this.wait();  // Отпускаем lock, ждем
        }
        
        buffer.add(item);
        System.out.println(Thread.currentThread().getName() + ": Produced " + item);
        
        // Пробуждаем потребителей
        this.notifyAll();
    }
    
    public synchronized T consume() throws InterruptedException {
        // Ждем, пока буфер заполнится
        while (buffer.isEmpty()) {
            System.out.println(Thread.currentThread().getName() + ": Buffer empty, waiting...");
            this.wait();  // Отпускаем lock, ждем
        }
        
        T item = buffer.poll();
        System.out.println(Thread.currentThread().getName() + ": Consumed " + item);
        
        // Пробуждаем производителей
        this.notifyAll();
        
        return item;
    }
}

// Тест
public class ProducerConsumerTest {
    public static void main(String[] args) throws InterruptedException {
        SharedBuffer<Integer> buffer = new SharedBuffer<>();
        
        Thread producer = new Thread(() -> {
            try {
                for (int i = 1; i <= 20; i++) {
                    buffer.produce(i);
                    Thread.sleep(100);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }, "Producer");
        
        Thread consumer = new Thread(() -> {
            try {
                for (int i = 0; i < 20; i++) {
                    buffer.consume();
                    Thread.sleep(200);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }, "Consumer");
        
        producer.start();
        consumer.start();
        
        producer.join();
        consumer.join();
        
        System.out.println("Done");
    }
}

Распространенные ошибки

// ❌ Ошибка 1: Вызов wait() вне synchronized
public void bad1() throws InterruptedException {
    this.wait();  // IllegalMonitorStateException
}

// ❌ Ошибка 2: if вместо while
public synchronized void bad2() throws InterruptedException {
    if (!condition) {
        this.wait();
    }
    // Spurious wakeup может разбудить без условия
}

// ❌ Ошибка 3: Использование notify() вместо notifyAll()
public synchronized void bad3() {
    this.notify();  // Может пробудить неправильный поток
}

// ✅ Правильно
public synchronized void good() throws InterruptedException {
    while (!condition) {
        this.wait();
    }
    // work
    this.notifyAll();
}

wait() vs современные подходы

// Старый способ (Object методы)
Object lock = new Object();
synchronized(lock) {
    while (!condition) {
        lock.wait();
    }
    // work
    lock.notifyAll();
}

// Модерный способ (java.util.concurrent.locks)
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();

lock.lock();
try {
    while (!isConditionMet()) {
        condition.await();
    }
    // work
    condition.signalAll();
} finally {
    lock.unlock();
}

// Еще лучше: высокоуровневые утилиты
CountDownLatch latch = new CountDownLatch(1);
// Вместо сложной синхронизации

Иерархия состояний потока

NEW
  ↓
RUNNABLE ←→ WAITING (wait() вызван)
  ↓          ↓
BLOCKED    (пробужден notify())
  ↓          ↓
TERMINATED ←┴─ RUNNABLE

Best Practices

  1. Используй notifyAll(), не notify()
  2. Используй while, не if, при проверке условия
  3. Всегда обрабатывай InterruptedException
  4. Для новых кодов используй java.util.concurrent, не Object методы
  5. Используй try-finally для освобождения lock'а

Методы wait/notify Object — фундамент многопоточности в Java, но в новых проектах лучше использовать более высокоуровневые инструменты из java.util.concurrent.

Какие знаешь методы класса Object, связанные с многопоточностью? | PrepBro