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. 三大价值:解耦、异步、限流
相关推荐
luteres24 分钟前
Mybatis+SSM整合笔记
java·开发语言·mybatis
whcyhhh26 分钟前
CTF‑MISC 隐写术完整学习笔记|图片隐写全题型 + 工具 + 实战例题
python·网络安全·ctf·misc·信息隐藏
郝学胜-神的一滴32 分钟前
《C++11 工程级应用01:告别冗长类型,开启简洁高效编码新时代》深度解读
开发语言·c++·算法·软件开发·系统设计
其实防守也摸鱼37 分钟前
推荐一个自动化教育SRC漏洞挖掘系统--AutoHunter
运维·开发语言·人工智能·学习·安全·web安全·自动化
码视野42 分钟前
多宠 RFID 颈圈识别与湿粮半导体制冷保鲜分餐喂食器解决方案(软硬件一体化)
大数据·人工智能·python
未若君雅裁44 分钟前
让模型按契约返回数据,LangChain 结构化输出实战
python·langchain
学习中.........2 小时前
Transformer 训练资源估算:以 CS336 GPT-2 XL 配置为例
人工智能·python·算法·机器学习·自然语言处理
覆东流7 小时前
7.Java数组
java·开发语言·后端
m0_719084117 小时前
限流和获取请求ip的方法
java
码行山野赴时序归途7 小时前
三道经典数组题:从暴力到最优的算法思维
c语言·开发语言·数据结构·算法·leetcode