阻塞队列。

一、阻塞队列是什么?

1.阻塞队列(BlockingQueue) 是一种 线程安全的队列 ,当队列为空或满时,线程会 自动阻塞等待

2.阻塞特性

a)队列为空,尝试出队列,出队列操作就会阻塞.阻塞到其他线程添加元素为止.

b)队列为满,尝试入队列,入队列操作也会阻塞.阻塞到其他线程取走元素为止.

阻塞队列,一个最主要的应用场景,就是实现"生产者消费者模型"多线程编程中,一种典型的编码技巧.

经典例子:厨房后厨

场景

厨师👨‍🍳负责做菜(生产者)

服务员💁负责端菜(消费者)

出餐台 = 阻塞队列

情况一: 出餐台满了

厨师做好的菜没地方放 只能等服务员端走

厨师被"阻塞"

情况二:出餐台空了

服务员没菜可端 只能等厨师做

服务员被"阻塞"

二、生产者消费者模型的两个重要优势

1.解耦合(不一定是两个线程之间,也可以是两个服务器之间)

本来是A和B耦合 现在成了A和队列耦合,B和队列耦合???

A的代码中就看不见B了 B的代码中也看不见A了.

A的代码中和B的代码中只能看到队列

降低耦合,是为了让后续修改的时候成本低,队列一般不会修改

之前AB耦合带来的问题:担心A的代码需要修改的时候,B也得同时改 反之亦然

2.削峰填谷

一般来说A这种上游的服务器,尤其是入口的服务器,干的活更简单,单个请求消耗的资源数少像B这种下游的服务器,通常承担更重的任务量,复杂的计算/存储工作,单个请求消耗的资源数更多.日常工作中,确实是会给B这样角色的服务器分配更好的机器.即使如此,也很难保证B承担的访问量能够比A更高.

趁着峰值过去了,B仍然继续消费数据.利用波谷的时间,来赶紧消费之前积压的数据

生产者消费者模型付出的代价

1)引入队列之后,整体的结构会更复杂.此时,就需要更多的机器,进行部署.生产环境的结构会更复杂.管理起来更麻烦.

2)效率会有影响

三、阻塞队列使用

Java标准库中,提供了现成的阻塞队列.BlockingQueue

java 复制代码
public static void main(String[] args) throws InterruptedException {
        BlockingQueue<String> queue = new LinkedBlockingQueue<>();
        queue.put("aaa");

        String elem = queue.take();

        System.out.println(elem);
    }

当队列无东西可拿时则会进入阻塞等待

可以设置阻塞队列容量大小

如果不设置capacity,默认是一个非常大的数值实际开发,一般建议大家能够设置上要求的最大值.否则队列可能变的非常大,导致把内存耗尽,产生 内存超出范围这样的异常....

java 复制代码
public static void main(String[] args) {
       BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(1000);

       Thread producer = new Thread(() -> {
           int n = 0;
           while (true){
               try {
                   queue.put(n);
                   System.out.println("生产元素" + n);
                   n++;

               } catch (InterruptedException e) {
                   throw new RuntimeException(e);
               }
           }
       },"producer");

       Thread consumer = new Thread(() -> {
           while (true){
               try {
                   Integer n = queue.take();
                   System.out.println("消费元素 " + n);
               } catch (InterruptedException e) {
                   throw new RuntimeException(e);
               }
           }
       },"consumer");
       producer.start();
       consumer.start();
    }

此处代码设置了容量大小为1000 而运行结果会发现生产元素和消费元素已经是跑到了百万去???

阻塞队列,没有提供一个"阻塞的获取队首元素的操作"直接运行,生产者和消费者两个线程的速度,旗鼓相当,所以很难见到阻塞效果。

此时就需要在生产者或消费者中加入sleep

四、模拟实现

java 复制代码
class MyBlockingQueue {
    //用数组作为底层存储结构
    private String[] data = null;

    //队首 指向下一个要取出的元素位置
    private int head = 0;

    //队尾 指向下一个要插入元素的位置
    private int tail = 0;

    //元素个数
    private int size = 0;

    public MyBlockingQueue(int capacity) {
        data = new String[capacity];
    }

    // 生产者调用的方法:向队列中放入元素
    public void put(String elem) throws InterruptedException {
        // 对当前对象加锁,保证线程安全
        synchronized (this) {
            // 使用 while 是为了防止线程被错误唤醒
            while (size >= data.length){
                this.wait();
            }
            //在队尾放入元素
            data[tail] = elem;
            tail++;
            // 如果 tail 到达数组末尾,则回到 0(循环队列)
            if(tail >= data.length){
                tail = 0;
            }

            size++;
            this.notify();
        }

    }

    public String take() throws InterruptedException {
        synchronized (this){
            while (size == 0){
                this.wait();
            }
            String ret = data[head];
            head++;
            if(head >= data.length){
                head = 0;
            }
            size--;
            this.notify();
            return ret;
        }
    }
}
public class Demo {
    public static void main(String[] args) {
        MyBlockingQueue queue = new MyBlockingQueue(1000);
        Thread producer = new Thread(() ->{
            int n = 0;
            while (true){
                try {
                    queue.put(n + "");
                    System.out.println("生产元素" + n);
                    n++;
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"producer");

        Thread consumer = new Thread(() ->{
            while (true){
                String n = null;
                try {
                    n = queue.take();
                    System.out.println("消费元素" + n);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"consumer");

        producer.start();
        consumer.start();
    }
}
相关推荐
Scott9999HH32 分钟前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
腻害兔38 分钟前
【若依项目-产品经理视角】深度拆解 RuoYi-Vue-Pro 商城模块:从商品管理到交易引擎,50 张表撑起一整套电商系统
java·大数据·vue.js·产品经理·ai编程
码智社2 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海2 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖3 小时前
JDK 26 新特性详解
java·开发语言
马优晨3 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区5 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大5 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai6 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
维天说6 小时前
CLI-Switch 2026年3月版历史设计:Hook、TTY 隔离与 JSON 状态
java·服务器·json