wait/notify 必须在 synchronized 块中调用,否则抛 IllegalMonitorStateException;推荐用 while 而非 if 防虚假唤醒;Condition 提供更清晰的等待队列;BlockingQueue 是生产者-消费者首选;volatile 不能替代同步机制。
直接调用 wait() 或 notify() 会抛出 IllegalMonitorStateException,因为 JVM 要求当前线程必须持有对象的监视器锁。这不是可选约定,是强制语义。
synchronized(obj) { obj.wait(); },不能只写 obj.wait();
synchronized(obj) { obj.notify(); }
while 而非 if 检查条件,避免虚假唤醒(spurious wakeup)public class Buffer {
private String data;
private boolean available = false;
public void put(String value) throws InterruptedException {
synchronized (this) {
while (available) { // 不用 if!
this.wait();
}
data = value;
available = true;
this.notifyAll(); // 用 notifyAll 更安全
}
}
public String take() throws InterruptedException {
synchronized (this) {
while (!available) {
this.wait();
}
available = false;
String result = data;
data = null;
this.notifyAll();
return result;
}
}
}
ReentrantLock 配合 Condition 可以摆脱 synchronized 的单一隐式锁限制,支持多个独立等待队列,逻辑更可控,也避免了 notify() 随机唤醒的问题。
Condition 实例绑定一个特定等待条件(如“缓冲区非空”“缓冲区非满”)await() 和 signal() 不再依赖对象锁的隐式语义,出错时异常信息更明确lock() 后调用 await(),且需在 finally 中 unlock()
public class BoundedBuffer {
private final Lock lock = new ReentrantLock();
private final Co
ndition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
private final String[] buffer = new String[10];
private int putIndex = 0, takeIndex = 0, count = 0;
public void put(String item) throws InterruptedException {
lock.lock();
try {
while (count == buffer.length) {
notFull.await();
}
buffer[putIndex] = item;
putIndex = (putIndex + 1) % buffer.length;
++count;
notEmpty.signal(); // 精准唤醒消费者
} finally {
lock.unlock();
}
}
public String take() throws InterruptedException {
lock.lock();
try {
while (count == 0) {
notEmpty.await();
}
String item = buffer[takeIndex];
takeIndex = (takeIndex + 1) % buffer.length;
--count;
notFull.signal();
return item;
} finally {
lock.unlock();
}
}
}
绝大多数线程协作问题本质是生产者-消费者模型。手写 wait/notify 或 Condition 容易出错,而 BlockingQueue 已经封装好线程安全、阻塞语义和容量控制,开箱即用。
ArrayBlockingQueue(有界,基于数组,性能稳定)和 LinkedBlockingQueue(可选界,基于链表,吞吐略高)最常用put() 和 take() 方法自动处理阻塞与唤醒,无需手动同步poll() + Thread.sleep() 来模拟阻塞——这是反模式,浪费 CPU 且延迟不可控BlockingQueuequeue = new ArrayBlockingQueue<>(10); // 生产者线程 new Thread(() -> { try { for (int i = 0; i < 100; i++) { queue.put("item-" + i); // 自动阻塞 } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }).start(); // 消费者线程 new Thread(() -> { try { while (true) { String item = queue.take(); // 自动阻塞 System.out.println("Consumed: " + item); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }).start();
volatile 只能保证变量读写的可见性与禁止重排序,**不提供原子性,也不构成 happens-before 关系来保障复合操作的正确性**。试图用它实现协作逻辑,几乎必然出错。
volatile boolean ready = false 控制线程启动顺序,再配合 while(!ready) Thread.yield(); —— 这不是协作,是忙等,且仍可能因指令重排导致未初始化数据被读取volatile 适合状态标志(如关闭信号),但绝不能用于“等待某条件成立后执行下一步”的通信逻辑wait/notify、Condition、BlockingQueue、CountDownLatch 或 CyclicBarrier 等明确建模同步语义的工具
线程协作的本质不是“让一个线程看到另一个线程改了什么”,而是“让一个线程在某个条件满足前暂停,并在条件满足时被可靠唤醒”。这个“暂停-唤醒”闭环,volatile 根本不参与。