阻塞队列实现

目录

一 . 什么是阻塞队列?

二. 阻塞队列的优点

三. 实现阻塞队列


一 . 什么是阻塞队列?

特点 : 先进先出

相比于普通队列 , 阻塞队列又有一些其他方面的功能 : 

1 . 线程安全

2 . 产生阻塞效果

   1) 如果队列为空 , 尝试出队列 , 就会出现阻塞 , 阻塞到队列不为空为止.

   2) 如果队列为满 , 尝试入队列 , 就会出现阻塞 , 阻塞到队列不为满为止.

运用阻塞队列 , 就可以实现"生产者消费者模型"

举个栗子 : 

        过年时 , 家里都要包饺子 , 准备好馅 与 面 , 擀面杖 , 然后大家都围在一个桌子上.

        一般就是一个人来负责专门用擀面杖来擀饺子皮 , 剩下的人负责包.

        如果说包饺子的人比较慢 , 擀的饺子皮都放了一桌了 , 那么擀饺子皮的人就可以休息一会.

        如果说擀饺子皮的人 , 擀的比较慢, 跟不上包饺子的人的速度了 , 那么休息的就是包饺子的人.

这就对应到了我们的生产者消费者模型 , 擀饺子皮的人就是生产者 , 包饺子的人就是消费者 , 休息意味着进入阻塞. 桌子就是相当于队列,擀饺子皮的人往里面放 , 包子饺子皮的人从里面拿.

Java标准库中的阻塞队列 : BlockingQueue

public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue queue = new LinkedBlockingDeque<>();
//        //指定capacity 容量的大小
//        BlockingQueue queue1 = new ArrayBlockingQueue<>(20);
        queue.put(12);
        queue.put(144);
        int n = queue.take();
        System.out.println(n);
    }
}

二. 阻塞队列的优点

假设有两个服务器 A B , A作为入口服务器用来接收用户的网络请求 , B作为应用服务器 , 用来给A提供一些数据~~

阻塞队列实现_第1张图片

在上述这种情况下 , A 与 B 的耦合性是非常高的 !!

在开发A的时候就要了解B提供的一些接口.

在开发B的时候也要充分了解到A是怎么调用的.

在这种情况下 , 如果想把B换做C, 那么A的代码就要进行一些改动

或者说如果B服务器挂了 , 也就有可能导致A服务器也顺带挂了.

根据上述这些问题 , 我们就可以采用"生产者消费者模型" . 

两大优点 : 

1 . 能够让多个服务器程序之间更充分的解耦合

2 . 能够对于请求进行"削峰填谷

阻塞队列实现_第2张图片

给A 与 B 之间加上了阻塞队列 .

这时 , A 与 B 对于对方的存在都是不知晓的.

如果再要加服务器的话 , 直接跟阻塞队列交互就可以了 .

这样就实现了两个模块之间低耦合

阻塞队列实现_第3张图片

未使用生产者消费者模型的时候 , 如果请求量突然暴涨.

由于A 和 B 直接调用的关系 , A收到了请求峰值, B也会同样有这个峰值.

假设A突然在某一时间收到的请求是平常的3倍.

A 作为入口服务器 , 计算量很轻, 请求量暴涨, 问题不大.

B 作为应用服务器 , 计算量可能很大 , 需要的系统资源也更多 , 服务器处理每个请求,都需要消耗硬件资源, 包括不限于(CPU , 内存 , 硬盘 , 带宽...) , 如果某个硬件资源达到瓶颈 , 此时服务器就挂了, 很有可能A服务器也就跟着挂了, 这给系统的稳定性带来了风险!

故如果使用生产者消费者模型则会更好 : 

 阻塞队列实现_第4张图片

第二个优点 "削峰填谷"

"削峰" :  A 收到的请求多了 , 给到阻塞队列 , 队列里的元素也多了 . 

             此时B仍然可以按照之前的速率来取元素. 队列帮B承担了压力.

"填谷" : 等这波请求峰值过去后, B 服务器仍然可以按照原有的速率处理队列中之前挤压的数据.

三. 实现阻塞队列

实现阻塞队列分三步

1 . 先实现一个普通队列

2 . 加上线程安全  (synchronized)

3 . 加上阻塞功能  (wait  notify)

package Demo2;

public class MyBlockingQueue {
    int[] arr = new int[20];
    //记录数组中的有效数字个数
    volatile int usedSize = 0;
    volatile int head = 0;
    volatile int tail = 0;

    // put方法
    synchronized public void put(int value) throws InterruptedException {
        //如果队列满 则进入阻塞状态等待
        if (arr.length == usedSize) {
            this.wait();
        }
        arr[tail] = value;
        tail++;
        if (tail >= arr.length) {
            tail = 0;
        }
        usedSize++;
        // 放入成功后 调用notify 唤醒阻塞状态下的take
        this.notify();
    }
    //take 方法
    synchronized public int take() throws InterruptedException {
        // 如果队列为空 则进入阻塞状态
        if (usedSize == 0) {
            this.wait();
        }
        int del = arr[head];
        head++;
        if (head >= arr.length) {
            head = 0;
        }
        usedSize--;
        // 唤醒put方法
        this.notify();
        return del;
    }
}

给 put 和 take 方法都加进行加锁操作 , 保证线程安全.

并且在队列为空 , 或者为满的情况下让线程进入阻塞状态.

每次放入或者弹出一个元素的时候 , 去唤醒另一个线程(避免另一个线程是处于阻塞状态)

在上述代码中 , 还少思考的一点 , 如果在代码复杂的情况下 :

wait 是可能被其他方法给中断的   (interrupt 方法)

此时wait其实等待的条件还没成熟了 , 就被提前唤醒了!

因此代码就可能不符合预期了.

阻塞队列实现_第5张图片

package Demo2;

public class MyBlockingQueue {
    int[] arr = new int[2];
    //记录数组中的有效数字个数
    volatile int usedSize = 0;
    volatile int head = 0;
    volatile int tail = 0;

    // put方法
    synchronized public void put(int value) throws InterruptedException {
        //如果队列满 则进入阻塞状态等待
        while (arr.length == usedSize) {
            this.wait();
        }
        arr[tail] = value;
        tail++;
        if (tail >= arr.length) {
            tail = 0;
        }
        usedSize++;
        // 放入成功后 调用notify 唤醒阻塞状态下的take
        this.notify();
    }
    //take 方法
    synchronized public int take() throws InterruptedException {
        // 如果队列为空 则进入阻塞状态
        while (usedSize == 0) {
            this.wait();
        }
        int del = arr[head];
        head++;
        if (head >= arr.length) {
            head = 0;
        }
        usedSize--;
        // 唤醒put方法
        this.notify();
        return del;
    }
}

附 : 写代码一般追求高内聚 , 低耦合 , 避免代码牵一发而动全身.

      高内聚 : 相关联的代码 , 分门别类的规制起来,找起来容易.

      低耦合 : 耦合两个模块之间的关联关系是强还是弱 , 关联越强,耦合越高.

你可能感兴趣的:(JAVAEE,java,开发语言)