目录
1,什么是阻塞队列
队列是一种数据的组织形式,它可以用链表活数组的形式实现,队列的规则是先入队的元素会先出队(先进先出)
阻塞队列在队列的基础上又加入了一些条件:
当插入元素时,先进行判断,队列是否已满,如果满了那就阻塞,等队列有空余位置的时候再进行插入操作
当取出元素时,先进行判断,队列是否为空,如果为空那就阻塞,等队列中有元素的时候再进行取出操作
2,阻塞队列的使用场景
2.1 生产者-消费者模型
生产者线程将任务放入阻塞队列,消费者从阻塞队列中取得需要处理的任务
当阻塞队列放满之后,生产者线程就会阻塞,等阻塞队列有空余之后,才会继续生产
当阻塞队列为空之后,消费者线程无法从阻塞队列中获取任务,消费者线程陷入阻塞,等生产者线程把新的任务放到阻塞队列之后,消费者线程才会继续消费
3 消息队列
对于阻塞队列的经典应用,基于生产者-消费者模型实现的
工作原理:
- 先进先出
- 给每一个消息打上一个标签
在消息队列中出队是按照消息类型出队的
消息队列就是在业务需求的趋势下,应用队列这个数据结构做一些自定义的功能开发,满足真是业务中的一些应用场景,类似这样的框架或者软件,我们把他们称为中间件
3.1 为什么要使用消息队列
3.1.1 解耦
程序应当尽量做到高内聚低耦合
高内聚就是一个模块内部的各部分之间的联系应该尽可能的紧密
低耦合是指不同模块间的相互联系或者依赖尽可能的小
看以下场景:
服务器A向服务器B提供请求,服务器B进行回应
在此模型中,服务器A必须时刻感知到服务器B的存在,在通信时双方必须知道对方的调用方法,参数等
再加入一个服务器C
新增一个服务器C和服务器A,B都有调用关系,因此需要对服务器A和服务器B的代码进行修改,测试和重新部署,费时费力,而且不管那个服务器出现问题,都会影响整个业务的流程,这就是模块间耦合过高的结果
我们可以使用消息队列作为中间件,服务器只需要从消息队列中拿取数据和任务并进行处理,不必再与其他部分进行通讯,一个服务器出现问题也不会影响到整个业务流程,减少了应用之间的交叉依赖,完成了解耦的操作
3.1.2 削峰填谷
如果应用会在某一段时间面临流量暴增的情况,而过了这段时间流量又会迅速下降的话,增加更多的硬件来实时处理这些数据显然是不划算的,而不增加硬件设施的话流量暴增会在系统中申请很多线程和资源,最终会将服务器资源耗尽出现崩溃
这时候我们可以使用消息队列
服务器A只需要将生产消息送到消息队列中,服务器B可以从消息队列中取出任务并执行,能执行多少就拿多少,不会出现崩溃的情况,这就是削峰,当流量迅速下降的时候,服务器B继续执行消息队列中积压的生产信息,这就是填谷
3.1.3 异步操作
在使用消息队列之后,服务器A不必等待服务器B的反馈,它只需要向消息队列添加元素即可,服务器B也只需要处理消息队列里的生产消息即可
4 阻塞队列的实现
4.1 通过JDK提供的API创建一个阻塞队列
public static void main(String[] args) throws InterruptedException {
//JDK提供的创建方式
BlockingQueue<Integer>queue = new LinkedBlockingQueue<>(3);
queue.put(1);
queue.put(2);
queue.put(3);
System.out.println("3");
// queue.put(4);
// System.out.println("4");
System.out.println(queue.take());
System.out.println(queue.take());
System.out.println(queue.take());
System.out.println("take了三个元素");
System.out.println(queue.take());
System.out.println("take了四个元素");
}
定义阻塞队列大小为3,添加了三个元素为满,也只能删除3个元素
JDK没有提供查看队首元素的线程安全方法
4.2 自己实现一个阻塞队列
使用数组来实现一个队列,put方法在size大于等于队列长度的时候会阻塞,因为队列已经放满了,需要take方法删除元素才能放入,take方法在执行完成后会唤醒put方法,重新开始进行锁竞争,使用while判断的原因是为了防止唤醒后条件依然不成立的情况(虚假唤醒),因此需要再次判断,如果队列仍然为满,则继续进行等待
public class MyBlockingQueue {
private Integer[]elementData = new Integer[3];
private volatile int head = 0;
private volatile int tail = 0;
private volatile int size = 0;
public MyBlockingQueue(){}
public MyBlockingQueue(int length){
elementData = new Integer[length];
}
//添加元素
public void put(Integer value) throws InterruptedException {
synchronized(this){
while(size>=elementData.length){//while解决虚假唤醒问题(可能被其他情况异常唤醒,因此应多次判断)
//队列已满
this.wait();
}
elementData[tail] = value;
size++;
if(tail==elementData.length-1){
tail=0;
}else {
tail++;
}
//添加新元素完成后唤醒其他线程
this.notifyAll();
}
}
//获取元素
public Integer take() throws InterruptedException {
synchronized (this){
while(size==0){
this.wait();
}
Integer v = elementData[head];
if(head==elementData.length-1){
head=0;
}else {
head++;
}
size--;
this.notifyAll();
return v;
}
}
}
生产者消费者模型测试MyBlockingQueue
public static void main(String[] args) {
MyBlockingQueue myBlockingQueue = new MyBlockingQueue();
//生产者线程
Thread produce = new Thread(()->{
int num = 0;
while(true){
try {
//生产好的元素加入队列
myBlockingQueue.put(num);
System.out.println("生产了:"+num+"个数");
num++;
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
produce.start();
//消费者线程
Thread consumer = new Thread(()->{
while(true){
try {//取出元素表示消费过程
System.out.println("消费了"+myBlockingQueue.take());
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
consumer.start();
}
由于程序设定中生产者生产更快,生产者将阻塞队列塞满之后,就陷入阻塞,消费者消费一个生产者就生产一个