阻塞队列。

一、阻塞队列是什么?

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();
    }
}
相关推荐
啦啦啦_99991 小时前
3. AI面试题之 FunctionCall
java
半夜修仙1 小时前
总结一下 Spring 中存取 Bean 的相关注解, 以及这些注解的用法.
java·笔记·学习·spring
彭于晏Yan1 小时前
Spring Cloud Security:Oauth2令牌存储
java·spring boot·spring cloud
不光头强1 小时前
ArrayList知识点
java·开发语言·windows
皙然2 小时前
吃透 Java 泛型
java
斌糖雪梨2 小时前
invokeBeanFactoryPostProcessors(beanFactory); 方法详解
java·后端·spring
摇滚侠2 小时前
SpringBoot 工程,不是所有的服务都引入了 spring-boot-starter-amqp 依赖,我应该使用什么条件注解,判断配置类是否生效
java·spring boot·spring
码云数智-大飞2 小时前
解锁数据库极速引擎:索引底层机制、聚簇与非聚簇之争及性能避坑指南
开发语言
花间相见2 小时前
【JAVA基础03】—— JDK、JRE、JVM详解及原理
java·开发语言·jvm