提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言(中间件,消息队列)
- 队列科普延伸
- 正文
- 一.阻塞队列是什么
- 特点
- 应用场景:
- 优点:
- 二、阻塞队列的具体使用:
- 1.标准库
- 2.自己实现(入队,出队)
- 三.完整代码
- 实现细节
前言(中间件,消息队列)
队列科普延伸
并不是所有的队列都是先进先出的:
优先级队列priorityQueue,根据优先级
消息队列:在队列的元素中,引入一个类型“topic”(业务上的类型),入队列没事,出队列的时候,会指定某个类型的元素,先出
消息队列在日常开发应用:
工作中经常把消息队列这样的数据结构,单独实现成一个程序,并且部署在一组服务器上
消息队列服务器,也就是咱们平时说的MQ,也就是一种常用的“”中间件“”
中间件就是一类通用的服务器的统称,例如mysql可以视为一个中间件,因为不同业务对数据存储需求差不多。
正文
一.阻塞队列是什么
是一个特殊队列,先进先出的
特点
- 线程安全
- 带有阻塞功能
a)如果队列满,继续入队列,入队列操作就会阻塞,直到队列不满,入队列才能完成
b)如果队列空,继续出队列,出队列操作也会阻塞,直到队列不空,出队列才能完成
应用场景:
生产者消费者模型:描述的是多线程协同工作的一种方式
优点:
1.使用阻塞队列,有利于代码解耦合
耦合:俩个模块之间的关联关系,关系越紧密,耦合越高,关系越不紧密,耦合越低
2.削峰填谷:
按照没有生产者消费者模型的写法,外面流量过来的压力,就会直接压倒在每个服务器上,如果摸个服务器的抗压能力不太行,就容易挂
使用阻塞队列,流量骤增时,a和队列承受了压力,b,c还是按照原来的节奏来消费数据,对于b,c冲击不大
类似于三峡水库:
二、阻塞队列的具体使用:
1.标准库
//系统实现的阻塞队列
public class demo22 {
public static void main(String[] args) throws InterruptedException {
BlockingDeque<Integer> blockingDeque = new LinkedBlockingDeque<>(100);
// //带有阻塞功能的入队列
blockingDeque.put(1);
blockingDeque.put(2);
blockingDeque.put(3);
Integer ret = blockingDeque.take();
System.out.println(ret);
ret = blockingDeque.take();
System.out.println(ret);
ret = blockingDeque.take();
System.out.println(ret);
ret = blockingDeque.take();
System.out.println(ret);
}
}
2.自己实现(入队,出队)
思路:
- 先实现一个普通队列(基于链表,基于数组)(循环队列)
首先记录下队首和队尾元素的位置: - [head,tail)表示队列中有效元素的范围
如何区分队列空还是满:加一个变量size记录个数
当tail走向尾时候,再插入元素先判满,不满,可以将tail置为0,当size=item.length时候队列满 - 加上线程安全
保证线程安全,关键就是加锁 - 加上阻塞的实现
如果队列空,出队列阻塞
如果队列为满,入队列阻塞 - 注意在同一个队列同一时刻,不会出现又空又满(阻塞条件相悖)
三.完整代码
class MyBlockingQueue {
private int[] items = new int[1000];
private volatile int head = 0;
private volatile int tail = 0;
private volatile int size = 0;
//入队列
public void put(int elem) throws InterruptedException {
synchronized (this) {
//判断队列是否满了,满了则不能插入
while (size >= items.length) {
this.wait();
}
//进行插入操作,把elem放到items里,放到tail指向的位置
items[tail] = elem;
tail++;
if (tail >= items.length) {
tail = 0;
}
size++;
this.notify();
}
}
//出队列,返回删除的元素的内容
public Integer take() throws InterruptedException {
synchronized (this) {
while (size == 0) {
this.wait();
}
//进行取元素操作
int ret = items[head];
head++;
if (head >= items.length) {
head = 0;
}
size--;
this.notify();
return ret;
}
}
实现细节
关于循环语句为什么是while,不是if
假设线程要进行取操作,当队列为空,take进入wait状态,具体啥时候唤醒是不确定的,当wait唤醒队列不一定非空,要进行多次判断(线程的抢占式执行)
1.wait被interrupt提前唤醒(没有继续等待put操作,导致队列依然是空,非法引用
2.多个线程的调度:
t1线程执行take操作,t2,t3竞争锁,t2线程拿到锁,插入元素,t1,t3竞争锁,t3拿到锁take走元素,此时t1再拿到锁时候,队列依然是空,要进行多组判断,