JUC 生产者消费者模式三种方式实现

518 阅读3分钟

生产者消费者模式是程序设计中非常常见的一种设计模式,被广泛运用在解耦、消息队列等场景

在现实世界中,我们把生产商品的一方称为生产者,把消费商品的一方称为消费者,有时生产者的生产速度特别快,但消费者的消费速度跟不上,俗称“产能过剩”,又或是多个生产者对应多个消费者时,大家可能会手忙脚乱。如何才能让大家更好地配合呢?这时在生产者和消费者之间就需要一个中介来进行调度,于是便诞生了生产者消费者模式。

总体大纲

image.png

BlockingQueue 方式

思路

核心就是通过一个阻塞队列来缓冲

生产者将生产的产品放入阻塞队列中即可,如果阻塞队列满了,则等阻塞队列有空位了再将产品放入。

消费者从阻塞队列中消费产品,如果阻塞队列为空,则等阻塞队列有产品了再消费。

代码实现

public static void blockingQueue() {
    // 阻塞队列,容量为 10
    BlockingQueue blockingQueue = new ArrayBlockingQueue(10);
    // 生产任务
    Runnable producer = () -> {
        while (true) {
            try {
                // 生产,放入到阻塞队列中去
                blockingQueue.put(new Object());
                System.out.println(Thread.currentThread().getName() + " 进行生产");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    };
    // 启动两个生产者线程进行生产
    new Thread(producer, "producer1").start();
    new Thread(producer, "producer2").start();

    // 消费任务
    Runnable consumer = () -> {
        while (true) {
            try {
                // 消费,从阻塞队列中拿
                blockingQueue.take();
                System.out.println(Thread.currentThread().getName() + " 进行消费");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    };
    // 启动两个消费者线程进行消费
    new Thread(consumer, "consumer1").start();
    new Thread(consumer, "consumer2").start();
}

阻塞队列三对获取和存储元素 API 的区别:

  • take 和 put 是阻塞的获取和存储元素的方法

  • poll 和 offer 是不阻塞的获取元素和存储元素的方法。,并且 poll 和 offer 可以指定超时时间。

  • add 和 remove 存取元素,队列满时 add 抛异常,队列空时 remove 抛异常

Condition 方式

思路

其实就是自己通过 Condition 模拟一个阻塞队列的实现,流程和上面一样。

代码实现

public class MyBlockingQueueForCondition {

    /**
     * 队列
     */
    private Queue queue;

    /**
     * 队列最大长度
     */
    private int max = 16;

    private ReentrantLock lock = new ReentrantLock();

    /**
     * 队列没有空
     */
    private Condition notEmpty = lock.newCondition();

    /**
     * 队列没有满
     */
    private Condition notFull = lock.newCondition();

    public MyBlockingQueueForCondition(int size) {
        this.max = size;
        queue = new LinkedList();
    }

    /**
     * 向队列中添加元素
     */
    public void put(Object o) throws InterruptedException {
        lock.lock();
        try {
            // 用 while 不用 if 的原因是防止虚假唤醒
            while (queue.size() == max) {
                // 阻塞生产者线程
                notFull.await();
            }
            queue.add(o);
            // 唤醒消费者线程
            notEmpty.signalAll();
        } finally {
            lock.unlock();
        }
    }

    /**
     * 向队列中获取元素
     */
    public Object take() throws InterruptedException {
        lock.lock();
        try {
            while (queue.size() == 0) {
                // 阻塞消费者线程
                notEmpty.await();
            }
            Object item = queue.remove();
            // 唤醒生产者线程
            notFull.signalAll();
            return item;
        } finally {
            lock.unlock();
        }
    }
}

wait / notifyAll 方式

思路

其实就是自己通过 wait / notifyAll 模拟一个阻塞队列的实现,流程和上面一样。

代码实现

class MyBlockingQueue {

    /**
     * 队列长度
     */
    private int maxSize;

    private LinkedList<Object> storage;

    public MyBlockingQueue(int size) {
        this.maxSize = size;
        storage = new LinkedList<>();

    }

    /**
     * 向队列中添加元素
     */
    public synchronized void put() throws InterruptedException {
        // 用 while 不用 if 的原因是防止虚假唤醒
        while (storage.size() == maxSize) {
            wait();
        }
        storage.add(new Object());
        notifyAll();
    }

    /**
     * 向队列中获取元素
     */
    public synchronized void take() throws InterruptedException {
        while (storage.size() == 0) {
            wait();
        }
        System.out.println(storage.remove());
        notifyAll();
    }
}