多线程协作-生产者消费者模式

本文探讨了生产者消费者模式,介绍了如何使用BlockingQueue保证线程安全,以及同步原语synchronized、wait、notifyAll在模式中的应用。重点讲解了生产者避免阻塞和消费者等待数据的策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 什么是生产者消费者模式?

​ 是用于多线程协作的设计模式,在生产者消费者模型中往往有两类线程:生产者线程、消费者线程,生产者线程通过数据缓冲区进行数据通信,使得消费者线程无需一直傻傻等待生产者线程产生数据大大提高了通信效率,同时数据缓冲区的存在降低了生产者和消费者直接的耦合度,而且还能缓冲生产速率和消费速率的不平衡。

生产者消费者模式模型图:

在这里插入图片描述

模型包含角色:

  • 生产者:提交用户请求,将用户请求封装成数据,提交到数据缓冲区。
  • 消费者:处理用户请求,将缓冲区中的任务/数据进行处理。
  • 数据缓冲区:存放数据/任务的容器(Queue)。
  • 任务/数据:生产者向数据缓冲区中提交的数据结构。
  • Main:测试生产者消费者模型的启动类。

模型应用场景:

  • JDK线程池,ThreadPoolExecutor线程池构造函数的其中一个核心参数就是BlockingQueue,任务队列,线程提交的任务当线程池繁忙的时候就会被放入到任务队列中(相当于数据缓冲区),等待被空闲的线程池线程消费。
  • 消息队列:RabbitMQ,Kafka,异步,解耦,削峰,这个就不用多说了。

2. 使用BlockingQueue实现线程安全的生产者消费者模式

关键点:BlockingQueue 提供的 put 和 tack 方法

  • put方法,当BlockingQueue未满时放入数据。
  • tack方法,当BlockingQueue不为空时获取数据。
// 任务
public class Task {
    private int tag; //该变量纯粹用于标记数据

    public Task() {}
    public Task(int tag) {
        this.tag = tag;
    }

    public int getTag() {
        return tag;
    }
}

//  生产者
public class Producer implements Runnable{
    private BlockingQueue<Task> queue; // 数据缓冲区
    private volatile boolean isRunning = true; // 生产者状态

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

    public void stop() {
        isRunning = false;
    }

    @Override
    public void run() {
        try {
            while (isRunning){
                Thread.sleep(1000); // 每隔一秒生产一次(让消费者有充足时间去消费)
                Task task = new Task(new Random().nextInt(100)); // 生产产品到数据缓冲区中, 随机数标识不同产品
                queue.put(task); // 如果共享缓冲区已满就等到有空间了再放入
                System.out.println(Thread.currentThread().getName() + " 生产了"+task.getTag());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
            Thread.currentThread().interrupt();
        }
    }
}

// 消费者
public class Consumer implements Runnable{

    private BlockingQueue<Task> queue; // 数据缓冲区
    private volatile boolean isRunning = true; // 消费者状态
    private Consumer() {}
    public Consumer(BlockingQueue<Task> queue) {
        this.queue = queue;
    }

    public void stop() {
        isRunning = false;
    }

    @Override
    public void run() {
        try {
            while (isRunning) {
                Task take = queue.take(); // 若缓冲区数据为空就等待有数据在获取
                System.out.println("消费了Task"+take.getTag());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
            Thread.currentThread().interrupt();
        }
    }
}

// 测试启动类
public class Main {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<Task> queue = new LinkedBlockingQueue<>(2);
        // 生产者
        Producer producer1 = new Producer(queue);
        Producer producer2 = new Producer(queue);
        Producer producer3 = new Producer(queue);
        // 消费者
        Consumer consumer = new Consumer(queue);
        // 线程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        // 提交三个生产者到线程池
        executorService.execute(producer1);
        executorService.execute(producer2);
        executorService.execute(producer3);

        // 提交一个消费者到线程池执行
        executorService.execute(consumer);
        
        // 观察执行结果...
    }
}

3. 使用synchronized、wait、notifyAll实现生产者消费者模式

PS:生产动作和消费动作都是相互排斥的,性能较低。

// 产品
public class Task {
    private int tag;

    public Task(){}
    public Task(int tag){
        this.tag = tag;
    }

    public int getTag() {
        return tag;
    }
}

// 测试启动类
public class Main {
    public static void main(String[] args) {
        DataBuffer dataBuffer = new DataBuffer();

        // 生产者
        Producer producer1 = new Producer(dataBuffer);
        Producer producer2 = new Producer(dataBuffer);
        Producer producer3 = new Producer(dataBuffer);

       // 消费者
        Consumer consumer = new Consumer(dataBuffer);

        // 线程池,执行生产者消费者任务
        ExecutorService executorService = Executors.newCachedThreadPool();
        executorService.execute(producer1);
        executorService.execute(producer2);
        executorService.execute(producer3);
        executorService.execute(consumer); // 可以将消费者注释掉,以检测生产者是否会超前生产
    }
}

// 通用的数据缓冲区接口
public interface GenericDataBuffer {
    void put(Task task) throws InterruptedException;

    Task tack() throws InterruptedException;
}

// 数据缓冲区实现
public class DataBuffer implements GenericDataBuffer{
    private final static int MAX_SIZE = 4; // 缓冲区最大容量
    private final AtomicInteger currentSize = new AtomicInteger(0);
    private final List<Task> buffer = new LinkedList<>();

    // 向缓冲区中提交数据
    public synchronized void put(Task task) throws InterruptedException {
        // 队列已满,休眠不进行数据提交
        while (currentSize.get() >= MAX_SIZE) {
                this.wait();
        }
        buffer.add(task);
        currentSize.incrementAndGet();
        // 缓冲区中已有数据,唤醒消费者线程进行消费
        this.notifyAll();
    }

    // 获取缓冲区中的数据
    public synchronized Task tack() throws InterruptedException {
        Task task;
        // 队列已空,消费者线程休眠停止消费
        while (currentSize.get()<=0) {
                this.wait();
        }
        task = buffer.remove(0);
        currentSize.decrementAndGet();
        this.notifyAll();
        return task;
    }
}

// 生产者
public class Producer implements Runnable {
    public boolean isRunning = true;
    public GenericDataBuffer buffer;
    private Producer() {}
    public Producer(GenericDataBuffer buffer) {
        this.buffer = buffer;
    }

    public void stop() {
        this.isRunning = false;
    }

    @Override
    public void run() {
        try {
            while (isRunning){
                Thread.sleep(1000); // 1 秒生产一次
                Task task = new Task(new Random().nextInt(100));
                buffer.put(task);
                System.out.println(Thread.currentThread().getName()+" 生产了Task "+task.getTag());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
            Thread.currentThread().interrupt();
        }
    }
}

// 消费者
public class Consumer implements Runnable {
    public boolean isRunning = true;
    public GenericDataBuffer buffer;

    private Consumer(){}
    public Consumer(GenericDataBuffer buffer){
        this.buffer = buffer;
    }

    public void stop() {
        this.isRunning = false;
    }

    @Override
    public void run() {
        try {
            while (isRunning) {
                Task task = buffer.tack();
                System.out.println(Thread.currentThread().getName()+" 消费了Task "+task.getTag());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
            Thread.currentThread().interrupt();
        }
    }
}

总结

​ 模型中的角色就是那几个:生产者生产产品放到数据缓冲区中,消费者去缓冲区拿产品自己去消费,构建一个启动类测试这个模型。

要注意的点就是两个:

  • 生产者不能生产的数量达到数据缓冲区的最大值就必须等待。
  • 消费者当数据缓冲区没有数据的时候就必须等待。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值