阻塞队列。

一、阻塞队列是什么?

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();
    }
}
相关推荐
初夏睡觉12 分钟前
c++1.3(变量与常量,简单数学运算详解),草稿公放
开发语言·c++
升职佳兴20 分钟前
C盘爆满自救:3步无损迁移应用数据到E盘(含回滚)
c语言·开发语言
ID_1800790547323 分钟前
除了 Python,还有哪些语言可以解析 JSON 数据?
开发语言·python·json
周末也要写八哥1 小时前
多进程和多线程的特点和区别
java·开发语言·jvm
惜茶2 小时前
vue+SpringBoot(前后端交互)
java·vue.js·spring boot
宁瑶琴2 小时前
COBOL语言的云计算
开发语言·后端·golang
杰克尼2 小时前
springCloud_day07(MQ高级)
java·spring·spring cloud
小陈工3 小时前
2026年4月2日技术资讯洞察:数据库融合革命、端侧AI突破与脑机接口产业化
开发语言·前端·数据库·人工智能·python·安全
Zarek枫煜3 小时前
C3 编程语言 - 现代 C 的进化之选
c语言·开发语言·青少年编程·rust·游戏引擎
阿kun要赚马内3 小时前
Python中元组和列表差异:底层结构分析
开发语言·python