1. Java 线程通信的基础概念
1.1 线程通信的重要性与模型
线程之间的协作 是多线程程序中实现正确行为的关键,而 wait/notify/notifyAll 则构成了 Java 语言层面的基本工具,用于在对象的监视器上实现等待、唤醒和条件判断。互斥与可见性是该模型的核心保障。本文聚焦于 Java 线程通信全解析:从 wait/notify/notifyAll 到 Condition(Lock)的实战应用与最佳实践,并逐步揭示不同实现的优劣与适用场景。
通过监视锁,Java 确保同一时刻只有一个线程进入临界区,其他进入等待队列的线程在被唤醒后重新竞争锁。正确的条件判断与循环等待模式是避免“伪唤醒”的关键。下面的示例将直观地展示等待与唤醒的基本用法。
public class WaitNotifyExample {
private final Object lock = new Object();
private boolean ready = false;
public void waitForReady() throws InterruptedException {
synchronized (lock) {
while (!ready) {
lock.wait(); // 放弃锁并进入等待
}
// 继续执行
}
}
public void setReady() {
synchronized (lock) {
ready = true;
lock.notifyAll(); // 通知等待线程
}
}
}
2. wait/notify/notifyAll 的工作机制与陷阱
2.1 基本用法与正确范式
在使用 wait、notify、notifyAll 时,必须在同一个 同步块或方法中对同一个对象的锁进行操作。循环检查条件是避免伪唤醒的重要设计。
notify 会唤醒等待队列中的一个线程,而 notifyAll 会唤醒所有等待线程,但具体谁获得 CPU 时段由调度器决定。为了避免死锁和错位唤醒,通常使用 while 而非 if 进行条件判断。
public class WaitNotifyDemo {
private final Object lock = new Object();
private boolean dataAvailable = false;
public void producer() {
synchronized (lock) {
dataAvailable = true;
lock.notifyAll();
}
}
public void consumer() {
synchronized (lock) {
while (!dataAvailable) {
try {
lock.wait();
} catch (InterruptedException ignored) {}
}
// 处理数据
}
}
}
3. 使用 Lock 与 Condition 替代 wait/notify
3.1 Condition 的语义与用法
Lock 提供了更灵活的锁获取策略和可重入特性,而 Condition 则像 对象的等待集合,为不同的等待条件提供独立的条件变量。
使用 ReentrantLock 与 Condition 可以避免一些与 synchronized 相关的陷阱,并且可以实现更细粒度的等待策略。每个 Condition 都有自己的等待队列,唤醒策略更可控。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Condition;
public class ConditionDemo {
private final Lock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
private final int[] buffer = new int[10];
private int count = 0;
public void put(int value) throws InterruptedException {
lock.lock();
try {
while (count == buffer.length) {
notFull.await();
}
buffer[count++] = value;
notEmpty.signal();
} finally {
lock.unlock();
}
}
public int take() throws InterruptedException {
lock.lock();
try {
while (count == 0) {
notEmpty.await();
}
int val = buffer[--count];
notFull.signal();
return val;
} finally {
lock.unlock();
}
}
}
4. 实战案例:生产者-消费者模型的实现
4.1 基于 Condition 的生产者-消费者
生产者-消费者是并发编程中的经典场景,使用 Condition 可以让生产者在缓冲区满时等待,并在有空间时被唤醒;消费者在缓冲区为空时等待,并在有数据时被唤醒。通过将不同条件分解为独立的变量,可以避免“错位唤醒”和竞争语义的混乱。
在高并发场景中,锁的粒度和条件变量的数量直接影响吞吐量与响应时间。合理设计队列边界和唤醒策略,有助于降低 上下文切换成本,提升整体性能。
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Condition;
public class ProducerConsumer {
private final Queue queue = new LinkedList<>();
private final int maxSize = 100;
private final Lock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
public void produce(int value) throws InterruptedException {
lock.lock();
try {
while (queue.size() == maxSize) {
notFull.await();
}
queue.offer(value);
notEmpty.signal();
} finally {
lock.unlock();
}
}
public int consume() throws InterruptedException {
lock.lock();
try {
while (queue.isEmpty()) {
notEmpty.await();
}
int val = queue.poll();
notFull.signal();
return val;
} finally {
lock.unlock();
}
}
}
5. 最佳实践与性能考量
5.1 设计原则与常见坑
在设计 Java 线程通信时,尽量降低锁粒度,使用 独立的条件变量来分离不同的等待条件,可以减少不必要的唤醒。
另外,避免在锁持有期间执行耗时操作,以减少竞争和上下文切换;对于等待超时的场景,优先使用带有超时参数的等待方法,以免“永久阻塞”线程。


