Java中wait和await的区别
Java 中 wait() 和 await() 都是用于线程等待的方法,但核心区别在于所属类、依赖条件和使用场景,前者依赖 对象监视器锁,后者依赖 Condition 条件对象。
1. 所属类与依赖锁机制不同
这是两者最根本的区别,决定了它们的使用前提。
wait():
- 属于
java.lang.Object类,是所有 Java 对象的默认方法。 - 依赖 synchronized 锁(对象监视器锁),必须在
synchronized代码块或方法中调用,否则会抛出IllegalMonitorStateException。 - 示例:
synchronized (lockObj) {lockObj.wait(); // 必须在 synchronized 块内,依赖 lockObj 的监视器锁 }
- 属于
await():
- 属于
java.util.concurrent.locks.Condition接口,需通过Lock接口(如ReentrantLock)的newCondition()方法创建。 - 依赖 Lock 锁(显式锁),必须在
lock()获得锁后、unlock()释放锁前调用,否则同样抛出异常。 - 示例:
Lock lock = new ReentrantLock(); Condition condition = lock.newCondition(); lock.lock(); try {condition.await(); // 依赖 Lock 锁,需在 lock() 后调用 } finally {lock.unlock(); }
- 属于
2. 唤醒方式不同
两者唤醒线程的方法不互通,需对应专属的唤醒 API。
wait():
- 需通过对应对象的
notify()(唤醒单个等待线程)或notifyAll()(唤醒所有等待线程)唤醒。 - 唤醒后,线程需重新竞争 synchronized 锁,获得锁后才能继续执行
wait()之后的代码。
- 需通过对应对象的
await():
- 需通过对应
Condition对象的signal()(唤醒单个等待线程)或signalAll()(唤醒所有等待线程)唤醒。 - 唤醒后,线程需重新竞争 Lock 锁,获得锁后才能继续执行
await()之后的代码。
- 需通过对应
3. 功能灵活性不同
await() 基于 Condition 提供了更丰富的功能,而 wait() 功能相对单一。
wait():
- 仅支持 “无参等待”(无限期等待,直到被唤醒)和 “带超时等待”(如
wait(1000),等待 1 秒后自动唤醒)。 - 所有等待线程共用一个 “等待队列”,
notify()只能随机唤醒一个线程,无法精准唤醒特定线程。
- 仅支持 “无参等待”(无限期等待,直到被唤醒)和 “带超时等待”(如
await():
- 除支持 “无参等待” 和 “超时等待”(如
await(1000, TimeUnit.MILLISECONDS)),还支持 “可中断等待”(await()可响应Thread.interrupt(),抛出InterruptedException,方便优雅停止线程)。 - 一个
Lock可创建多个Condition对象(对应多个 “等待队列”),例如用condition1.await()和condition2.await()让不同线程等待不同条件,唤醒时通过condition1.signal()精准唤醒特定队列的线程,灵活性更高。
- 除支持 “无参等待” 和 “超时等待”(如
4. 使用场景不同
wait():
- 适合简单的线程同步场景,如基础的生产者 - 消费者模型(仅一个生产队列、一个消费队列),且不追求精准唤醒。
- 依赖
synchronized,代码写法更简洁(无需手动释放锁,synchronized会自动释放)。
await():
- 适合复杂的线程同步场景,如多条件的生产者 - 消费者模型(例如 “队列满时生产者等待”“队列空时消费者等待”,需两个
Condition分别控制)、需要中断等待的场景。 - 依赖
Lock,需手动在finally中释放锁,但能实现更精细的线程控制。
- 适合复杂的线程同步场景,如多条件的生产者 - 消费者模型(例如 “队列满时生产者等待”“队列空时消费者等待”,需两个
总结对比表
| 对比维度 | wait() | await() |
|---|---|---|
| 所属类 | Object 类 | Condition 接口 |
| 依赖锁 | synchronized 锁(隐式锁) | Lock 锁(显式锁,如 ReentrantLock) |
| 调用前提 | 必须在 synchronized 块 / 方法内 | 必须在 lock () 后、unlock () 前 |
| 唤醒方法 | 对应对象的 notify ()/notifyAll () | 对应 Condition 的 signal ()/signalAll () |
| 功能扩展 | 仅支持无参 / 超时等待 | 支持无参 / 超时 / 可中断等待,多条件队列 |
| 适用场景 | 简单同步,无需精准唤醒 | 复杂同步,需精准唤醒或中断 |
下面分别用 wait()(基于 synchronized)和 await()(基于 ReentrantLock + Condition)实现经典的 “生产者 - 消费者” 模型,核心逻辑均为 “队列满时生产者等待,队列空时消费者等待”,方便直观对比两者差异。
一、用 wait () + notifyAll () 实现
依赖 synchronized 隐式锁,所有等待线程共用一个 “对象监视器”,唤醒时需用 notifyAll() 避免线程漏唤醒(notify() 随机唤醒可能只唤醒同类线程,导致死锁)。
import java.util.LinkedList;
import java.util.Queue;// 产品队列(共享资源)
class ProductQueue {private final Queue<String> queue = new LinkedList<>();private final int MAX_SIZE = 5; // 队列最大容量// 生产者:生产产品放入队列public synchronized void produce(String product) throws InterruptedException {// 队列满时,生产者等待while (queue.size() >= MAX_SIZE) {System.out.println("队列满,生产者" + Thread.currentThread().getName() + "等待...");this.wait(); // 释放synchronized锁,进入等待状态}// 生产产品queue.offer(product);System.out.println("生产者" + Thread.currentThread().getName() + "生产:" + product + ",当前队列:" + queue);// 唤醒所有等待线程(可能是消费者,也可能是其他生产者)this.notifyAll();}// 消费者:从队列取出产品public synchronized void consume() throws InterruptedException {// 队列空时,消费者等待while (queue.isEmpty()) {System.out.println("队列空,消费者" + Thread.currentThread().getName() + "等待...");this.wait(); // 释放synchronized锁,进入等待状态}// 消费产品String product = queue.poll();System.out.println("消费者" + Thread.currentThread().getName() + "消费:" + product + ",当前队列:" + queue);// 唤醒所有等待线程(可能是生产者,也可能是其他消费者)this.notifyAll();}
}// 测试类
public class WaitNotifyDemo {public static void main(String[] args) {ProductQueue queue = new ProductQueue();// 启动2个生产者线程for (int i = 1; i <= 2; i++) {new Thread(() -> {try {for (int j = 1; j <= 3; j++) {queue.produce("产品" + j);Thread.sleep(500); // 模拟生产耗时}} catch (InterruptedException e) {Thread.currentThread().interrupt();}}, "P" + i).start();}// 启动2个消费者线程for (int i = 1; i <= 2; i++) {new Thread(() -> {try {for (int j = 1; j <= 3; j++) {queue.consume();Thread.sleep(800); // 模拟消费耗时}} catch (InterruptedException e) {Thread.currentThread().interrupt();}}, "C" + i).start();}}
}
二、用 await () + signalAll () 实现
依赖 ReentrantLock 显式锁,通过两个 Condition 分别创建 “生产者等待队列” 和 “消费者等待队列”,可精准唤醒目标线程(生产者唤醒消费者,消费者唤醒生产者),减少无效唤醒。
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;// 产品队列(共享资源)
class ProductQueueWithLock {private final Queue<String> queue = new LinkedList<>();private final int MAX_SIZE = 5; // 队列最大容量private final Lock lock = new ReentrantLock(); // 显式锁// 两个Condition:分别控制生产者等待、消费者等待private final Condition producerCond = lock.newCondition();private final Condition consumerCond = lock.newCondition();// 生产者:生产产品放入队列public void produce(String product) throws InterruptedException {lock.lock(); // 手动获取锁try {// 队列满时,生产者进入“生产者等待队列”while (queue.size() >= MAX_SIZE) {System.out.println("队列满,生产者" + Thread.currentThread().getName() + "等待...");producerCond.await(); // 释放Lock锁,进入生产者等待队列}// 生产产品queue.offer(product);System.out.println("生产者" + Thread.currentThread().getName() + "生产:" + product + ",当前队列:" + queue);// 精准唤醒消费者(只有消费者在等,避免唤醒生产者)consumerCond.signalAll();} finally {lock.unlock(); // 手动释放锁,必须在finally中确保执行}}// 消费者:从队列取出产品public void consume() throws InterruptedException {lock.lock(); // 手动获取锁try {// 队列空时,消费者进入“消费者等待队列”while (queue.isEmpty()) {System.out.println("队列空,消费者" + Thread.currentThread().getName() + "等待...");consumerCond.await(); // 释放Lock锁,进入消费者等待队列}// 消费产品String product = queue.poll();System.out.println("消费者" + Thread.currentThread().getName() + "消费:" + product + ",当前队列:" + queue);// 精准唤醒生产者(只有生产者在等,避免唤醒消费者)producerCond.signalAll();} finally {lock.unlock(); // 手动释放锁,必须在finally中确保执行}}
}// 测试类
public class AwaitSignalDemo {public static void main(String[] args) {ProductQueueWithLock queue = new ProductQueueWithLock();// 启动2个生产者线程for (int i = 1; i <= 2; i++) {new Thread(() -> {try {for (int j = 1; j <= 3; j++) {queue.produce("产品" + j);Thread.sleep(500); // 模拟生产耗时}} catch (InterruptedException e) {Thread.currentThread().interrupt();}}, "P" + i).start();}// 启动2个消费者线程for (int i = 1; i <= 2; i++) {new Thread(() -> {try {for (int j = 1; j <= 3; j++) {queue.consume();Thread.sleep(800); // 模拟消费耗时}} catch (InterruptedException e) {Thread.currentThread().interrupt();}}, "C" + i).start();}}
}
三、核心差异对比(从代码中可见)
| 对比点 | wait () 实现 | await () 实现 |
|---|---|---|
| 锁控制 | 依赖 synchronized 隐式锁,自动释放 / 获取 | 依赖 Lock 显式锁,需手动 lock()/unlock() |
| 等待队列 | 所有线程共用一个 “对象监视器队列” | 可通过多个 Condition 拆分多个等待队列 |
| 唤醒精准度 | 需用 notifyAll() 唤醒所有线程(可能包含同类线程) | 用 signalAll() 精准唤醒目标队列线程(如生产者只唤醒消费者) |
| 异常处理 | 无需手动处理锁释放(synchronized 自动保障) | 必须在 finally 中释放锁,避免死锁 |
