Java多线程生产者消费者模型:synchronized与wait/notify实现

季宁
季宁
发布时间:2026-10-14 | 6人浏览过
原创

生产者消费者是Java多线程编程中的核心面试题,旨在解决多个线程对共享资源的竞争问题。本文将通过一个完整的Java代码示例,演示如何利用 synchronized 关键字确保同一时刻只有一个线程访问容器,并结合 wait() 和 notifyAll() 方法实现线程间的协调通信。

一、核心逻辑与问题分析

生产者消费者问题的核心在于“同步”与“互斥”。我们需要定义一个固定容量的容器,并遵循以下规则:

  • 互斥访问:无论是生产还是消费,同一时间只能有一个线程操作容器,防止数据错乱。
  • 满则停(生产者):当容器装满时,生产者必须暂停等待,直到有空间释放。
  • 空则停(消费者):当容器为空时,消费者必须暂停等待,直到有新商品产生。
  • 唤醒机制:生产者放入商品后需通知消费者;消费者取走商品后需通知生产者。

二、环境准备与代码结构

为了实现上述逻辑,我们定义了以下几个关键类:

  1. Goods:代表具体的商品信息,如ID。
  2. Container:作为共享的仓库资源,负责处理加锁、入栈(push)、出栈(pop)以及线程状态管理。
  3. Producer:生产者线程,循环执行“生产-放入-休眠”。
  4. Consumer:消费者线程,循环执行“取出-消费-休眠”。

三、真实操作步骤:完整代码实现

第1步:构建共享容器与同步逻辑

目的:创建线程安全的队列,利用内置锁和等待队列控制流程。
动作:在 Container 类中,使用数组存储商品。方法上使用 synchronized 修饰以确保原子性。

class Container {
    private int index = 0;
    Goods[] goods = new Goods[6];

    // 生产商品(入栈)
    public synchronized void push(Goods good) {
        // 如果满了,生产者是挂起
        while (index == goods.length) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 存放商品并移动索引
        goods[index] = good;
        index++;
        // 通知所有等待的线程(唤醒消费者)
        notifyAll();
    }

    // 消费商品(出栈)
    public synchronized Goods pop() {
        // 如果空了,消费者挂起
        while (index == 0) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 移动索引,获取商品
        index--;
        Goods good = goods[index];
        // 通知所有等待的线程(唤醒生产者)
        notifyAll();
        return good;
    }
}

第2步:实现生产者与消费者线程

目的:分别定义生产和消费的行为及循环条件。
动作:Producer 和 Consumer 均实现 Runnable 接口,持有容器的引用,模拟生产或消耗过程。

class Producer implements Runnable {
    Container con;

    public Producer(Container con) {
        this.con = con;
    }

    public void run() {
        for (int i = 0; i < 20; i++) {
            Goods good = new Goods(i);
            con.push(good);
            System.out.println("生产了:" + good);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class Consumer implements Runnable {
    Container con;

    public Consumer(Container con) {
        this.con = con;
    }

    public void run() {
        for (int i = 0; i < 20; i++) {
            Goods good = con.pop();
            System.out.println("消费了:" + good);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

第3步:启动主程序测试

入口:main 方法。 参数:创建一个 Container 实例,并分别传入给生产者、消费者线程对象。 结果:观察控制台打印顺序,验证是否满足“先进先出”且不会出现死锁或并发错误。

public class ProducerConsumerTest {
    public static void main(String[] args) {
        Container con = new Container();
        Producer p = new Producer(con);
        Consumer c = new Consumer(con);
        
        new Thread(p).start();
        new Thread(c).start();
    }
}

四、完成判断与排错指南

成功标准:
1. 程序无报错终止。
2. 生产者未生产超过6个商品前,消费者能正常消费。
3. 生产者填满容器后不再打印,直到消费者取出商品后才继续。

常见问题排查:
- 漏掉 while 判断:如果在判断条件(如 `if (index==goods.length)`)后直接调用 `wait()`,可能会因为虚假唤醒导致后续逻辑出错。必须使用 `while` 循环来检查条件是否真的满足。
- 丢失 this.notifyAll():如果在 wait() 之前忘记调用唤醒方法,线程将永远处于挂起状态,导致程序卡死。

五、简要总结

这个示例展示了Java中最基础的线程同步原语。虽然现代开发中常用 `java.util.concurrent` 包下的阻塞队列(如 `BlockingQueue`)来简化此过程,但理解 synchronized、wait 和 notify 的组合机制,对于深入理解JVM内存模型和多线程设计至关重要。

以上就是Java多线程生产者消费者模式的详细内容,更多关于Java并发编程的资料请关注本站其它相关文章!

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表