← Назад к вопросам
Какие знаешь методы класса 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():
- Проверяет, находится ли текущий поток в synchronized блоке
- Если нет → IllegalMonitorStateException
- Если да → освобождает lock
- Переводит поток в WAITING состояние
- Добавляет в wait set объекта
- Ждет, пока другой поток вызовет 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
- Используй notifyAll(), не notify()
- Используй while, не if, при проверке условия
- Всегда обрабатывай InterruptedException
- Для новых кодов используй java.util.concurrent, не Object методы
- Используй try-finally для освобождения lock'а
Методы wait/notify Object — фундамент многопоточности в Java, но в новых проектах лучше использовать более высокоуровневые инструменты из java.util.concurrent.