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. 三大价值:解耦、异步、限流
相关推荐
爱讲故事的1 小时前
操作系统第一讲复习:为什么学习操作系统,以及操作系统到底在做什么?
linux·开发语言·windows·学习·ubuntu·c#
笨蛋不要掉眼泪1 小时前
Java并发编程:Executors框架类深度解析
java·开发语言·并发
南极企鹅1 小时前
深入理解 MVCC:数据库并发控制的基石
java·数据库·mysql
凯瑟琳.奥古斯特2 小时前
力扣1235:加权区间调度最优解
java·python·算法·leetcode·职场和发展
_童年的回忆_2 小时前
【php】在linux下PHP安装amqp扩展
linux·开发语言·php
想不到ID了2 小时前
第八篇: 登录注册功能实现
java·javascript
郑洁文2 小时前
基于Python的网络入侵检测系统
网络·python·php
AIMath~2 小时前
python中的uv命令揭秘
开发语言·python·uv
码语智行2 小时前
shp文件生成
java