Java 多线程5——阻塞式队列BlockingQueue的原理 + 消费者生产者模型 + 代码模拟实现


前言

本人是一个刚刚上路的IT新兵,菜鸟!分享一点自己的见解,如果有错误的地方欢迎各位大佬莅临指导,如果这篇文章可以帮助到你,劳请大家点赞转发支持一下!

本篇文章分享的内容主要是阻塞式队列,以及代码模拟实现阻塞式队列。


一、阻塞式队列是什么?

阻塞队列是一种特殊的队列. 也遵守 “先进先出” 的原则。

阻塞队列能是一种线程安全的数据结构, 并且具有以下特性:

  • ✨当队列满的时候, 继续入队列就会阻塞, 直到有其他线程从队列中取走元素。
  • ✨当队列空的时候, 继续出队列也会阻塞, 直到有其他线程往队列中插入元素。

阻塞队列的一个典型应用场景就是 “生产者消费者模型”,这是一种非常典型的开发模型。

二、生产者消费者模型

什么是生产者消费者模型

🎉🎉🎉生产者消费者模式就是通过一个容器来解决生产者和消费者的 强耦合 问题。(耦合的大概意思是指模块之间的依赖程度,耦合程度越低越好,避免代码之间牵一发而动全身)想详细了解可以去大佬博客学习,附大佬博客:什么是耦合性?

生产者消费者模型的益处

生产者与消费者直接通讯
在这里插入图片描述
这是四个消费者向生产者服务器发出请求,假如是一万个,十万个,一亿个消费者向生产者服务器发出请求,那么服务器还能承受住吗??🦉🦉🦉
因此阻塞式队列就发挥他的作用了。

在这里插入图片描述

🧑🏻‍💻🧑🏻‍💻🧑🏻‍💻生产者和消费者彼此之间不直接通讯,而通过阻塞式队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞式队列,消费者不找生产者要数据,而是直接从阻塞式队列里取。

  1. 🎊🎊阻塞式队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
    比如在 “抢演唱会门票” 场景下, 服务器在同一时刻可能会收到大量的支付购票请求,如果直接处理这些支付购票请求,服务器可能扛不住(每个支付购票请求的处理都需要比较复杂的流程). 这个时候就可以把这些请求都放到一个阻塞队列中, 然后再由消费者线程慢慢的来处理每个支付购票请求。

  2. 🎊🎊阻塞式队列也能使生产者和消费者之间 解耦和。
    就比如,饭店后厨里的洗菜工切菜工,洗菜工只负责洗菜,切菜工只负责切洗好的菜。
    洗菜工就是"生产者",切菜工就是"消费者"。
    洗菜工不关心切菜工用什么切(能切就行,无论是用菜刀切,用武士刀切,用水果刀切),切菜工也不关心洗菜工用什么洗菜(能洗就行,无论是用凉水洗,用热水洗,用洗菜机洗)。

三、标准库中的阻塞式队列

在 Java 标准库中内置了阻塞队列. 如果我们需要在一些程序中使用阻塞队列, 直接使用标准库中的即可。

🎭BlockingQueue 是一个接口,真正实现的类有 链式存储的LinkedBlockingQueue 顺序存储的ArrayBlockingQueue

🧑🏻‍💻 put 方法用于阻塞式的入队列, take 用于阻塞式的出队列。

BlockingQueue 也有 offer, poll, peek 等方法, 但是这些方法不带有阻塞特性。

阻塞式队列实现生产者消费者模型:

    public static void main(String[] args) {
        BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<>();

        // 生产者模型
        Thread thread1 = new Thread(() -> {
            int value = 0;
            while (true) {
                try {
                    // 入队列
                    blockingQueue.put(value);
                    System.out.println("生产者:" + value);
                    value++;
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        // 消费者模型
        Thread thread2 = new Thread(() -> {
           while (true) {
               try {
                   // 出队列
                   int value = blockingQueue.take();
                   System.out.println("消费者:" + value);
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
        });
        thread1.start();
        thread2.start();
    }

运行结果:
在这里插入图片描述

阻塞式队列的特性

队列空时

    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<>();

        // 生产者模型
        Thread thread1 = new Thread(() -> {
            int value = 0;
            while (true) {
                try {
                    // 入队列
                    blockingQueue.put(value);
                    System.out.println("生产者:" + value);
                    value++;
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        // 消费者模型
        Thread thread2 = new Thread(() -> {
           while (true) {
               try {

                   System.out.println("消费者来消费了");
                   // 出队列
                   int value = blockingQueue.take();
                   System.out.println("消费者:" + value);
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
        });
        thread2.start();
        // 先启动消费者模型线程,后启动生产者线程
        Thread.sleep(3000);
        thread1.start();

    }

运行结果:
在这里插入图片描述
原因:消费者模型线程先运行的,当消费者模型线程运行到
"int value = blockingQueue.take();"这个语句时,阻塞队列里是空的,因为线程会在这个语句进行阻塞等待。

这是take()方法的源代码
在这里插入图片描述
如果队列里数据个数为0则调用await方法。(await方法与wait方法类似)当有数据入队列时,再唤醒这个线程。

队列满时

在这里插入图片描述

在这里插入图片描述
🥲🥲🥲如果不手动设置队列容量大小,那么他就是整数类型的最大值。

    public static void main(String[] args) throws InterruptedException {
    	// 设置阻塞式队列容量大小为5
        BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<>(5);

        // 生产者模型
        Thread thread1 = new Thread(() -> {
            int value = 0;
            while (true) {
                try {
                    // 入队列
                    blockingQueue.put(value);
                    System.out.println("生产者:" + value);
                    value++;
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        // 只启动生产者模型
        thread1.start();

    }

🎡运行结果:
在这里插入图片描述

队列满了之后,就会等待,等待有数据出队列后,再往队列里存。

四、代码模拟实现阻塞式队列

public class MyBlockingQueue {
    // 阻塞队列

    // 使用循环队列
    // 设置队列大小为1000
    private int[] items = new int[1000];


    volatile private int head = 0;// 队首下标
    volatile private int tail = 0;// 队尾下标
    volatile public int size = 0;// 数据个数

    // 入队列
    synchronized public void put(int elem) throws InterruptedException {
        // 可能有多个线程调用put方法,
        // 因此该队列的最后一个空位可能会被其他线程先占用,
        // 因此要用while来循环判断。
        while (size == items.length) {
            // 队列满了,阻塞等待
            this.wait();
        }
        // 把新元素放入tail队尾位置
        items[tail] = elem;
        // 更新tail位置
        tail++;
        // tail到达数组末尾,从0开始
        if(tail == items.length) {
            tail = 0;
        }
        // 更新数据个数
        size++;
        // 唤醒线程
        this.notify();
    }

    // 出队列

    synchronized public Integer take() throws InterruptedException {
        // 1.可能有多个线程调用take方法,
        // 因此该数据可能会被其他线程先取得。
        // 2.wait方法可能是被其他方法中断,
        // 代码就不会符合预期结果
        // 因此要用while来循环判断。
        while (size == 0) {
            // 队列为空,阻塞等待
            this.wait();
        }
        // 出队head队首下标数据
        int value = items[head];
        head++;
        // head到达数组末尾,从0开始
        if(head == items.length) {
            head = 0;
        }
        // 更新数据个数
        size--;
        // 唤醒线程
        this.notify();
        // 出队列
        return value;

    }

    public static void main(String[] args) {
        MyBlockingQueue queue = new MyBlockingQueue();

        // 生产者
        Thread thread1 = new Thread(()->{
            int value = 0;
            while (true) {
                try {
                    queue.put(value);
                    System.out.println("生产:" + value);
                    value++;
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        // 消费者
        Thread thread2 = new Thread(()->{
            while (true) {
                try {
                    int value = queue.take();
                    System.out.println("消费:" + value);
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread1.start();
        thread2.start();
    }

}

在这里插入图片描述

总结

以上就是今天要分享的内容,本文介绍了多线程中的阻塞式队列,他可以有效的帮助代码模块之间进行解耦和,可以大大提高编写与修改多线程代码的效率🥰🥰🥰🥰🥰🥰,如果帮助到你了,那么就打赏作者点赞收藏加关注吧🙌🙌🙌!

路漫漫不止修身,也养性。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
可以使用Java中的BlockingQueue实现消费者生产者问题。BlockingQueue是一个线程安全的队列,它提供了put()和take()方法,分别用于生产和消费。当队列为空时,take()方法会阻塞线程,直到队列中有元素;当队列满时,put()方法会阻塞线程,直到队列中有空闲位置。 以下是一个简单的示例代码: import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; public class ProducerConsumerExample { private static final int QUEUE_CAPACITY = 10; private static final int NUM_PRODUCERS = 2; private static final int NUM_CONSUMERS = 3; public static void main(String[] args) { BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(QUEUE_CAPACITY); for (int i = 0; i < NUM_PRODUCERS; i++) { new Thread(new Producer(queue)).start(); } for (int i = 0; i < NUM_CONSUMERS; i++) { new Thread(new Consumer(queue)).start(); } } static class Producer implements Runnable { private final BlockingQueue<Integer> queue; public Producer(BlockingQueue<Integer> queue) { this.queue = queue; } @Override public void run() { try { for (int i = 0; i < 10; i++) { queue.put(i); System.out.println("Produced: " + i); Thread.sleep(1000); } } catch (InterruptedException e) { e.printStackTrace(); } } } static class Consumer implements Runnable { private final BlockingQueue<Integer> queue; public Consumer(BlockingQueue<Integer> queue) { this.queue = queue; } @Override public void run() { try { while (true) { Integer value = queue.take(); System.out.println("Consumed: " + value); Thread.sleep(2000); } } catch (InterruptedException e) { e.printStackTrace(); } } } }

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值