设计模式之一——堵塞队列

堵塞队列呢是一种更为复杂的队列,他对比简单的队列有两个特性:1)线程安全;2)堵塞:a)队列为空时,尝试出队列,出队列操作就会堵塞,直到有新的元素添加进来为止;b)队列满了时,尝试入队列操作时会发生堵塞,直至队列被取走为止。

生产者消费者模型

堵塞队列呢有一个非常主要的场景:实现"生产者消费者模型"。什么是生产者消费者模型呢?

就拿这个服务器举例:这里直接A服务器请求B服务器,B服务器响应A服务器这种就不是生产者消费者模型。

当我们加入一个中转栈的时候就是实现了生产者消费者模型了,当A服务器想要去请求B服务器时,A服务器直接在中间栈中取即可,B服务器的响应也是直接发送给中间栈即可,这个中间栈呢也是我们今天要学的------堵塞队列

生产者消费者模型的优缺点

1.解耦合

通过上述服务器与服务器之间呢我们可以发现,当我们需要更改服务器A/B时,服务器B/A也会受到影响,这时这两个服务器就是高耦合了,但如果加入堵塞队列,我们更改某个服务器的代码时就不需要管另一个服务器的业务了,他们之间通过了堵塞队列来连接,而堵塞队列里的逻辑也不会有这两个服务器业务复杂。

2.削峰填谷

我们上过大学都知道,每当抢课时学校的官网都会崩溃,这就是因为进入官网的人太多了,所以A服务器一般都会有某个时刻点进去的人多,但B服务器的响应又有限,所以当我们直接连接两个服务器时可能造成服务器崩溃。加入了堵塞队列后,当点击A服务器的人多了就会对堵塞队列请求的多,但B服务器还是以之前的速度响应,不会影响到B服务器,但趁着这个峰值过去了服务器B还是以之前的响应速度响应传给堵塞队列,故而等下次点击量波峰时可以有效的缓解。

3.缺点

1.使代码变得复杂

引入队列之后,整体的结果会变得更复杂,此时,就需要更多的机器来部署,生产环境的结果复杂,管理起来也复杂。

2.效率降低

不使用堵塞队列时,服务器与服务器之间的请求和响应都是直接的,引入了堵塞队列他们还得加载到队列中,当数量达到一定量时会影响效率。

堵塞队列的使用

BlockingQueue的介绍

堵塞队列的类是BlockingQueue:

通过观察我们发现,new时会给我们很多对象,这里我们就讨论三个:ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue;

ArrayBlockingQueue:这时一个基于数组实现的堵塞队列;

LinkedBlockingQueue:这是一个基于链表实现的堵塞队列;

PriorityBlockingQueue:这时基于优先级队列实现的堵塞队列;

BlockingQueue这个类呢我们点击去发现他是继承了Queue类的,使用当我们使用这个类时是可以当作普通类来使用的;但我们学BlockingQueue的目的是学习堵塞队列,故而我们就讲讲堵塞队列的使用。

BlockingQueue的使用

这样子我们就简单的创建了一个容量为100的堵塞队列。

当我们使用put方法时就是往堵塞队列中加入元素了,当队列满了在添加时就会堵塞等待。

弹出队列是take(),当队列为空时就会堵塞等待。

堵塞队列的使用就讲到这里,我们重点讲解堵塞队列的实现!!!!

堵塞队列的实现

堵塞队列的实现主要涉及到一下几个问题:

1)怎么插入元素

2)怎么取出元素

3)怎么实现堵塞

1.插入与删除元素

我们实现堵塞队列主要以数组的方式来实现,插入和删除元素可以定义一个头指针和尾指针,插入与删除就是头指针和尾指针加减。

2.怎么实现堵塞

我们之前学过wait和notify,这两个就可以很好的实现堵塞等待,当数组满了的时候就使用wait等待,等到有元素的删除即可发出notify来截至堵塞。空队列也是如此。

代码的实现

复制代码
class MyBlockingQueue{
    private String[] date = null;
    private int head = 0;//头指针
    private int tail = 0;//尾指针
    private int size = 0;//元素个数

    public MyBlockingQueue(int capacity) {
        date = new String[capacity];
    }
    public void put(String elem){
        if(size >= date.length){
            //堵塞等待
        }
        date[tail] = elem;
        tail++;
        if(tail >= date.length){
            tail = 0;
            //因为队列是先进先出的,当tail大于或等于了数组长度时,
            // 说明数组添加元素从头开始了,这里也不要害怕head没有走,因为有堵塞等待。
        }
    }
    public String take(){
        if(size == 0){
            //队列为堵塞等待
        }
        String ret = date[head];
        head++;
        if(head >= date.length){
            head =0;
            //因为队列是先进先出的,当队列走到了尽头的时候数组,
            // 因为put是从头开始来的,所以令head为0,
            // 当然也不要担心head和tail撞见,因为有堵塞等待
        }
        size--;
        return ret;
    }
}

第一步,我们设计出了基本的框架,这时插入和删除已经完成,但还差堵塞等待:我们得考虑wtai和notify在哪里加入?

复制代码
class MyBlockingQueue{
    private String[] date = null;
    private int head = 0;//头指针
    private int tail = 0;//尾指针
    private int size = 0;//元素个数

    public MyBlockingQueue(int capacity) {
        date = new String[capacity];
    }
    public void put(String elem) throws InterruptedException {
        synchronized (this){
            if(size >= date.length){
                this.wait();
                //堵塞等待
            }
            date[tail] = elem;
            tail++;
            if(tail >= date.length){
                tail = 0;
                //因为队列是先进先出的,当tail大于或等于了数组长度时,
                // 说明数组添加元素从头开始了,这里也不要害怕head没有走,因为有堵塞等待。
            }
            size++;
            this.notify();
        }

    }
    public String take() throws InterruptedException {
        synchronized (this){
            if(size == 0){
                this.wait();
                //队列为堵塞等待
            }
            String ret = date[head];
            head++;
            if(head >= date.length){
                head =0;
                //因为队列是先进先出的,当队列走到了尽头的时候数组,
                // 因为put是从头开始来的,所以令head为0,
                // 当然也不要担心head和tail撞见,因为有堵塞等待
            }
            size--;
            this.notify();
            return ret;
        }
    }
}

wait和notify呢可以加的也很简单,wait可以直接在 if(size == 0)和 if(size >= date.length)里边加,因为这里就是为了他们堵塞等待的,notify呢可以加载最后面,因为此时也快要解锁了让这两个方法能更好的竞争锁。

但这里还面临这一个问题,我们知道堵塞是可以通过其他方法唤醒的,比如Interrupt,当wait被其他方法唤醒时会出现队列满了/空了也会执行下去造成越界,而出现bug,所以我们可以把两个if改成while循环,此时即使wait提前被唤醒了也还会经过while判断是否成立,成立即退出,不成立则继续循环,故而最终代码如下:

复制代码
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 (size >= data.length) {
                // 队列满了. 需要阻塞的
                // return;
                this.wait();
            }
            data[tail] = elem;
            tail++;
            if (tail >= data.length) {
                tail = 0;
            }

            // tail = (tail + 1) % data.length;

            size++;
            this.notify();
        }
    }

    public String take() throws InterruptedException {
        synchronized (this) {
            while (size == 0) {
                // 队列空了. 需要阻塞
                // return null;
                this.wait();
            }
            String ret = data[head];
            head++;
            if (head >= data.length) {
                head = 0;
            }
            size--;
            this.notify();
            return ret;
        }
    }
}
相关推荐
冷崖2 小时前
桥模式-结构型
c++·设计模式
会员果汁2 小时前
19.设计模式-命令模式
设计模式·命令模式
茶本无香2 小时前
设计模式之六—组合模式:构建树形结构的艺术
java·设计模式·组合模式
橘色的喵2 小时前
嵌入式 C++ 高性能流式架构的设计
数码相机·设计模式
会员果汁3 小时前
20.设计模式-职责链模式
设计模式
她和夏天一样热17 小时前
【设计模式】工厂方法模式在开发中的真实应用
设计模式·工厂方法模式
烤麻辣烫1 天前
23种设计模式(新手)-9单例模式
java·开发语言·学习·设计模式·intellij-idea
资生算法程序员_畅想家_剑魔1 天前
Java常见技术分享-设计模式的六大原则
java·开发语言·设计模式
刀法如飞1 天前
从零手搓一个类Spring框架,彻底搞懂Spring核心原理
java·设计模式·架构设计