阻塞队列
阻塞队列是什么
阻塞队列是一种特殊的队列. 也遵守 “先进先出” 的原则.
- 当队列满的时候, 继续入队列就会阻塞, 直到有其他线程从队列中取走元素
- 当队列空的时候, 继续出队列也会阻塞, 直到有其他线程往队列中插入元素
阻塞队列的一个典型应用场景就是 生产者消费者模型. 这是一种非常典型的开发模型.
生产者消费者模型
制作手机有两种方式
- 每个工厂包揽制作手机的全部流程, 包括制作组件, 拼接组装, 测试…
- 不同工厂负责不同的模块, 一些工厂负责制作组件, 另一些工厂负责组装成手机.
第二种方式就是生产者消费者模型.
制作组件的工厂就是生产者, 组装手机的工厂就是消费者. 放置组件的仓库就是交易场所/阻塞队列(生产者制作组件, 存入; 消费者使用组件, 取出)
这两种方式比较下来, 显然是第二种方式比较高效.
生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题.
生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取.
优势:
- 阻塞队列也能使生产者和消费者之间解耦.
假如外网向服务器发起了一个充值请求.
如果B挂了, 会对A产生影响; 如果A挂了, 会对B产生影响; 如果再添加一个服务器C, 就需要对A的代码进行较大的改动.
这样看, A和B之间的耦合就很明显.
如果使用生产者消费者模型, 引入阻塞队列, 就能有效解决上述问题.
A将请求放在队列中, B直接从队列里取请求, A与B之间没有直接交互, AB之间的耦合度大大减少.
但是响应的效率也会有所降低.
-
阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力.
服务器收到来自客户端的请求数目不是一成不变的, 可能会因一些突发事件引起请求数目暴增.
这些暴增的请求就能存放在阻塞队列中, 不会导致A, B承受不了大量请求而崩溃.
阻塞队列的实现
class MyBlockingQueue {//加上volatile保证内存可见性//使用一个String类型的数组来保存元素, 假设这里只存Stringprivate volatile String[] items = new String[1000];//指向队列的头部private volatile int head = 0;//指向队列的尾部的下一个元素, 队列中有效数组的元素范围[head, tail)private volatile int tail = 0;//当前的元素个数private volatile int size = 0;/*** 线程安全: 先给put和take进行加锁, 保证在多线程调用的时候能够线程安全* 实现阻塞:* + 当队列空了或者满了, 就引发阻塞.* @param src* @throws InterruptedException*/public void put(String src) throws InterruptedException {synchronized (this) {//此处的while目的不是为了循环, 而是借助循环的方式, 使线程被唤醒之后, 再次确认一下条件是否成立, 防止线程被唤醒时, 队列还是满着的状态. 此处不能用ifwhile (size >= items.length) {//只要队列满了, 阻塞, 当队列有空间后再被唤醒this.wait();}items[tail] = src;tail++;size++;if(tail >= items.length) {tail = 0;}//size++后, 原本空的队列有元素了, 所以因队列空而阻塞的线程可以被唤醒了this.notify();}}public String take() throws InterruptedException {synchronized (this) {while (size == 0) {//只要队列为空, 阻塞, 当队列中有元素后再被唤醒this.wait();}String elem = items[head];head++;size--;if (head >= items.length) {head = 0;}//size--后, 原本满的队列不满了, 所以因队列满而阻塞的线程可以被唤醒了this.notify();return elem;}}
}