Java 生产者-消费者模型详解

生产者-消费者模型是多线程编程中最经典的同步协作模型 ,核心解决:生产者生产数据→放入缓冲区→消费者从缓冲区取数据 ,实现解耦、异步、限流,避免生产速度和消费速度不匹配导致的数据丢失/阻塞。

一、核心概念

  1. 生产者线程:负责生产数据,放入共享缓冲区
  2. 消费者线程:负责从缓冲区取出数据,消费数据
  3. 共享缓冲区:线程安全的容器(队列/列表),是协作核心
  4. 同步机制:必须保证缓冲区空时消费者等待、缓冲区满时生产者等待;生产/消费后唤醒对方

二、Java 实现方式(3种常用方案)

方式1:Object wait/notify 实现(基础版)

利用 Java 内置的 wait()、notifyAll() 实现线程等待/唤醒,最基础、面试必考。

java 复制代码
import java.util.LinkedList;
import java.util.Queue;

// 共享缓冲区
class SharedBuffer {
    private final Queue<Integer> queue = new LinkedList<>();
    private final int capacity; // 缓冲区最大容量

    public SharedBuffer(int capacity) {
        this.capacity = capacity;
    }

    // 生产者:放入数据
    public synchronized void produce(int value) throws InterruptedException {
        // 缓冲区满,生产者等待
        while (queue.size() == capacity) {
            System.out.println("缓冲区已满,生产者等待...");
            wait();
        }
        queue.add(value);
        System.out.println("生产者生产:" + value + ",缓冲区大小:" + queue.size());
        notifyAll(); // 唤醒等待的消费者
    }

    // 消费者:取出数据
    public synchronized void consume() throws InterruptedException {
        // 缓冲区空,消费者等待
        while (queue.isEmpty()) {
            System.out.println("缓冲区为空,消费者等待...");
            wait();
        }
        int value = queue.poll();
        System.out.println("消费者消费:" + value + ",缓冲区大小:" + queue.size());
        notifyAll(); // 唤醒等待的生产者
    }
}

// 生产者线程
class Producer implements Runnable {
    private final SharedBuffer buffer;

    public Producer(SharedBuffer buffer) {
        this.buffer = buffer;
    }

    @Override
    public void run() {
        int value = 1;
        try {
            while (true) { // 持续生产
                buffer.produce(value++);
                Thread.sleep(500); // 模拟生产耗时
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

// 消费者线程
class Consumer implements Runnable {
    private final SharedBuffer buffer;

    public Consumer(SharedBuffer buffer) {
        this.buffer = buffer;
    }

    @Override
    public void run() {
        try {
            while (true) { // 持续消费
                buffer.consume();
                Thread.sleep(1000); // 模拟消费耗时
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

// 测试主类
public class ProducerConsumerDemo {
    public static void main(String[] args) {
        SharedBuffer buffer = new SharedBuffer(3); // 缓冲区容量3
        new Thread(new Producer(buffer), "生产者").start();
        new Thread(new Consumer(buffer), "消费者").start();
    }
}

关键知识点:

  • 必须用 while 循环判断缓冲区状态(防止虚假唤醒)
  • wait() 会释放锁,sleep() 不会释放锁
  • notifyAll() 唤醒所有等待线程,比 notify() 更安全

方式2:Lock/Condition 实现(进阶版)

用 ReentrantLock + Condition 替代 synchronized,支持精准唤醒生产者/消费者,效率更高。

java 复制代码
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

class SharedBuffer {
    private final Queue<Integer> queue = new LinkedList<>();
    private final int capacity;
    private final ReentrantLock lock = new ReentrantLock();
    private final Condition notFull = lock.newCondition();  // 生产者等待条件
    private final Condition notEmpty = lock.newCondition(); // 消费者等待条件

    public SharedBuffer(int capacity) {
        this.capacity = capacity;
    }

    public void produce(int value) throws InterruptedException {
        lock.lock();
        try {
            while (queue.size() == capacity) {
                notFull.await(); // 生产者等待
            }
            queue.add(value);
            System.out.println("生产:" + value + ",缓冲区:" + queue.size());
            notEmpty.signal(); // 精准唤醒消费者
        } finally {
            lock.unlock(); // 必须手动释放锁
        }
    }

    public void consume() throws InterruptedException {
        lock.lock();
        try {
            while (queue.isEmpty()) {
                notEmpty.await(); // 消费者等待
            }
            int value = queue.poll();
            System.out.println("消费:" + value + ",缓冲区:" + queue.size());
            notFull.signal(); // 精准唤醒生产者
        } finally {
            lock.unlock();
        }
    }
}

优势:

  • 两个 Condition 分离等待队列,避免无效唤醒
  • 手动加锁/释放锁,灵活性更高

方式3:BlockingQueue 实现(推荐,开发首选)

Java 并发包提供的线程安全阻塞队列 (ArrayBlockingQueue/LinkedBlockingQueue),内置阻塞机制,代码极简。

java 复制代码
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

// 生产者
class Producer implements Runnable {
    private final BlockingQueue<Integer> queue;

    public Producer(BlockingQueue<Integer> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        int value = 1;
        try {
            while (true) {
                queue.put(value++); // 队列满则自动阻塞
                System.out.println("生产:" + (value-1));
                Thread.sleep(500);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

// 消费者
class Consumer implements Runnable {
    private final BlockingQueue<Integer> queue;

    public Consumer(BlockingQueue<Integer> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        try {
            while (true) {
                int value = queue.take(); // 队列空则自动阻塞
                System.out.println("消费:" + value);
                Thread.sleep(1000);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

// 测试
public class BlockingQueueDemo {
    public static void main(String[] args) {
        // 容量为3的阻塞队列
        BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(3);
        new Thread(new Producer(queue)).start();
        new Thread(new Consumer(queue)).start();
    }
}

核心优势:

  • 无需手动写同步、等待、唤醒逻辑
  • 开箱即用,工业级稳定,实际开发首选
  • put()/take() 自带阻塞,offer()/poll() 非阻塞

三、三种方案对比

实现方式 优点 缺点 适用场景
Object wait/notify 基础、面试必考 代码繁琐、易出错、效率低 学习、面试
Lock/Condition 精准唤醒、高效 代码稍复杂 高级并发场景
BlockingQueue 极简、安全、高效 封装度高,底层原理隐藏 生产环境、实际开发

四、核心作用

  1. 解耦:生产者和消费者不直接通信,通过缓冲区交互
  2. 异步:生产和消费并发执行,无需同步等待
  3. 削峰限流:缓冲区平衡生产/消费速度,避免系统崩溃

总结

  1. 生产者-消费者核心是共享缓冲区 + 线程同步
  2. 基础面试用 wait/notify,实际开发用 BlockingQueue
  3. 必须处理线程安全 和虚假唤醒(用while循环)
  4. 三大价值:解耦、异步、限流
相关推荐
supabc12323 分钟前
欧盟电子发票(XRechnung/Peppol/ZUGFeRD)踩坑记:用 Python 不装 Java 跑官方校验规则
xml·python·开源·电子发票·1024程序员节
ZhengEnCi44 分钟前
AI Agent工具调用防护层怎么写-一份能直接跑的Python四层拦截实现
python·llm·agent
ZhengEnCi1 小时前
AI Agent工具误调用怎么解决-从参数校验到高危二次确认的四层工程防线
python·llm·agent
金銀銅鐵2 小时前
[Java] 用 GUI 展示 EnumSet 增减元素时的内部变化(下)
java·后端·ai编程
AI人工智能+电脑小能手2 小时前
一次请求打几个下游,这个Java并行任务工具类6个方法按序聚合
java·线程池·并发编程·1024程序员节·批量处理·并行任务
镜子AI2 小时前
镜子AI工程教程:用Python实现关键词布局检查器与语义改写查重(附完整代码)
人工智能·python·microsoft
小oo呆3 小时前
【学习心得】Python协程
python
paopaokaka_luck3 小时前
心理咨询管理系统(WebSocket实时沟通、基于用户的协同过滤、 ECharts多维分析、 预约业务流程、预约校验与定时处理)
java·vue.js·spring boot·mysql·spring·echarts·1024程序员节
信誓旦旦的程序猿3 小时前
【Python量化实战宝典 #22】涨跌停、连板情绪手册:情绪指标构建(Python 可跑)
python·1024程序员节·股票数据api·股票数据api接口·股票api数据接口·股票量化数据api·股票量化数据接口
gCode Teacher 格码致知4 小时前
Pandas教学-21:subplots的使用
python·pandas·1024程序员节