Ответ
Хотя высокоуровневые конструкции из пакета java.util.concurrent (JUC) часто предпочтительнее, базовые механизмы synchronized, wait() и notifyAll() всё ещё имеют свои нишевые применения.
1. synchronized
Когда использовать: Для простейших случаев синхронизации доступа к общему ресурсу в рамках одного JVM, когда достаточно встроенной блокировки монитора.
public class SimpleCounter {
private int count;
// synchronized гарантирует атомарность инкремента
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
Почему не всегда: synchronized — это непрерываемая блокировка. Для более сложных сценариев (таймауты, честность, раздельные условия ожидания) лучше использовать ReentrantLock из JUC.
2. wait() и notifyAll()
Когда использовать: Для реализации шаблона «Ожидание-Уведомление» (Wait-Notify) внутри пользовательских примитивов синхронизации или пулов, где нужно координировать потоки на основе изменения состояния.
Классический пример: Простой bounded buffer (очередь ограниченной ёмкости)
public class SimpleBlockingQueue<T> {
private final Queue<T> queue = new LinkedList<>();
private final int maxSize;
public SimpleBlockingQueue(int maxSize) { this.maxSize = maxSize; }
public synchronized void put(T item) throws InterruptedException {
while (queue.size() == maxSize) {
wait(); // Ждём, пока потребитель не освободит место
}
queue.add(item);
notifyAll(); // Уведомляем ожидающих потребителей
}
public synchronized T take() throws InterruptedException {
while (queue.isEmpty()) {
wait(); // Ждём, пока производитель не добавит элемент
}
T item = queue.remove();
notifyAll(); // Уведомляем ожидающих производителей
return item;
}
}
Критически важные правила:
- Всегда вызывайте
wait(),notify(),notifyAll()только внутри блока/методаsynchronized(поток должен владеть монитором объекта). - Всегда используйте
wait()в цикле while, проверяющем условие. Пробуждение (notify) может быть ложным (spurious wakeup). - Предпочитайте
notifyAll()notify(), если нет строгих требований к производительности, чтобы разбудить все ожидающие потоки.
В 90% случаев лучше выбрать из JUC:
- Вместо собственной реализации блокирующей очереди —
LinkedBlockingQueue. - Для координации потоков —
CountDownLatch,CyclicBarrier,Semaphore. - Для условного ожидания —
ConditionизReentrantLock(более гибкий аналогwait/notify).
Вывод: synchronized, wait(), notifyAll() — это низкоуровневый фундамент. Их стоит использовать для простой синхронизации или при реализации специфичных структур данных, в остальных случаях выбирайте готовые, оптимизированные и более безопасные классы из java.util.concurrent.*.