在Java多线程编程中,生产者消费者模式是经典的线程协作场景,核心逻辑是生产者生产数据放入共享容器,消费者从容器中取出数据消费,当容器满时生产者暂停生产,容器空时消费者暂停消费。Object类的wait和notify方法可以很好地实现这种协作逻辑,不需要引入复杂的并发工具类。

wait与notify的核心特性
wait和notify是Object类的原生方法,所有Java对象都具备这两个方法,使用时需要遵循以下核心规则:
- 调用wait、notify、notifyAll方法的线程必须先获取该对象的监视器锁(也就是synchronized锁),否则会抛出IllegalMonitorStateException异常。
- wait方法会让当前线程释放已获取的监视器锁,进入该对象的等待队列,直到其他线程调用该对象的notify或notifyAll方法唤醒。
- notify方法会随机唤醒该对象等待队列中的一个线程,被唤醒的线程不会立刻执行,需要重新获取监视器锁之后才能继续执行。
- 为了避免虚假唤醒问题,wait方法通常需要放在循环条件中判断,而不是if条件中。
生产者消费者模式实现思路
实现该模式需要三个核心部分:共享容器、生产者线程、消费者线程,具体逻辑如下:
- 共享容器:通常使用有固定容量的队列,当容器满时生产者调用容器的wait方法暂停,当容器空时消费者调用容器的wait方法暂停。
- 生产者逻辑:生产数据放入容器后,如果容器之前是空的,调用notify唤醒等待的消费者;如果放入后容器已满,调用wait暂停自身。
- 消费者逻辑:从容器取出数据后,如果容器之前是满的,调用notify唤醒等待的生产者;如果取出后容器已空,调用wait暂停自身。
完整代码示例
以下是基于wait和notify实现的简单生产者消费者示例,共享容器使用自定义的有界队列:
import java.util.LinkedList;
import java.util.Queue;
// 有界共享队列
class SharedQueue {
private final Queue<Integer> queue = new LinkedList<>();
private final int capacity;
public SharedQueue(int capacity) {
this.capacity = capacity;
}
// 生产者放入数据
public synchronized void produce(int data) throws InterruptedException {
// 循环判断避免虚假唤醒
while (queue.size() == capacity) {
System.out.println("队列已满,生产者等待");
wait();
}
queue.offer(data);
System.out.println("生产者生产数据:" + data + ",当前队列大小:" + queue.size());
// 唤醒等待的消费者
notify();
}
// 消费者取出数据
public synchronized int consume() throws InterruptedException {
// 循环判断避免虚假唤醒
while (queue.isEmpty()) {
System.out.println("队列已空,消费者等待");
wait();
}
int data = queue.poll();
System.out.println("消费者消费数据:" + data + ",当前队列大小:" + queue.size());
// 唤醒等待的生产者
notify();
return data;
}
}
// 生产者线程
class Producer implements Runnable {
private final SharedQueue queue;
public Producer(SharedQueue queue) {
this.queue = queue;
}
@Override
public void run() {
try {
for (int i = 0; i < 10; i++) {
queue.produce(i);
Thread.sleep(100);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
// 消费者线程
class Consumer implements Runnable {
private final SharedQueue queue;
public Consumer(SharedQueue queue) {
this.queue = queue;
}
@Override
public void run() {
try {
for (int i = 0; i < 10; i++) {
queue.consume();
Thread.sleep(150);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public class ProducerConsumerDemo {
public static void main(String[] args) {
SharedQueue queue = new SharedQueue(5);
Thread producerThread = new Thread(new Producer(queue));
Thread consumerThread = new Thread(new Consumer(queue));
producerThread.start();
consumerThread.start();
}
}
使用注意事项
在使用wait和notify实现生产者消费者时,需要注意以下几点:
- 一定要先获取对象的监视器锁再调用wait或notify,否则会抛出异常。
- wait必须放在while循环中判断条件,不能放在if中,防止虚假唤醒导致逻辑错误。
- 如果等待的线程较多,建议使用notifyAll代替notify,避免某个线程一直无法被唤醒的问题。
- wait和sleep的区别:wait会释放锁,sleep不会释放锁,二者不要混淆使用。
常见问题解答
为什么wait要放在synchronized代码块中?
因为wait方法调用后需要释放锁,如果当前线程没有持有锁,就没有锁可以释放,所以必须先获取锁才能调用wait,而synchronized是获取对象监视器锁的标准方式。
虚假唤醒是什么?
虚假唤醒指的是线程在没有被其他线程调用notify、notifyAll或者中断的情况下,从wait状态中唤醒的情况,虽然这种情况很少发生,但Java规范允许这种可能,所以需要用循环条件反复校验,确保唤醒后条件确实满足再执行后续逻辑。
Object.waitObject.notify生产者消费者线程协作修改时间:2026-06-13 23:21:29