阻塞队列
阻塞队列的概念
队列相信我们已经不陌生了 之前也学过很多队列 比如: 普通队列 和 优先级队列 两种
这两种队列都是线程不安全的
而我们讲的阻塞队列 刚好可以解决线程安全问题 也是先进先出 并且带有阻塞功能.
阻塞功能是怎么回事呢
就是如果入队的时候阻塞队列为满 ,则当前线程就会进入阻塞状态,阻塞到有出队列操作才会再唤醒线程继续执行 ,相同 如果出队时阻塞队列为空,则当前线程也会进入阻塞状态 直到有新元素入队操作才会唤醒继续执行该线程.
在Java标准库中,BlockingQueue这个就是标准库提供的阻塞队列. 阻塞队列在实际开发中使用的场景十分广泛. 在阻塞方法中 有独特的进队列和出队列的方法 其他一些普通的方法阻塞队列也有 ,但是使用阻塞队列一般都不会再使用普通队列的方法了.
消息队列
消息队列这种数据结构其实也是阻塞队列的一种 ,也带有阻塞特性.它不是普通的先进先出,而是通过topic这样的参数来对数据进行分类,出队列的时候,指定topic,每个topic下的数据是先进先出. 消息队列只要应用于生产者消费者模型.
生产者消费者模型
什么是生产者和消费者模型呢 ?
举个例子:
就相当于两个人制作饺子 要分为两步 一步是擀饺子皮 一步是包饺子. 要两个人分工合作,实际上 要包饺子就得先擀饺子皮 那擀饺子皮的人相当于生产者 包饺子皮的人就相当于消费者 擀好的饺子皮要用一个容器来盛放 ,而这个容器就相当于阻塞队列/消息队列. 有了这个容器就会有阻塞功能 ,现在A擀饺子皮 B包饺子 如果B包饺子包得快 A擀饺子皮慢 那容器里面的饺子皮很快就会空了 这时候B就要阻塞等待A擀饺子皮 每擀一个就包一个饺子 ,相反 如果A擀饺子皮擀得比较快 B包饺子慢,那么容器很快就会满了,这样A就会阻塞等待 B包饺子 B每包一个 A就擀一个.
上面图片就是生产者和消费者模型
你们想一想 在实际开发中为什么要用到这个生产者消费者模型 ? 相当于在开发中 有两种服务器 :请求服务器Q和应用服务器Y 如果QY直接传递消息 ,没有阻塞队列的情况下,如果Q突然请求有很多的时候,Y的请求也就会跟着暴涨 ,但Y服务器是应用服务器,他处理任务的过程很复杂 是重量级的 这样就会让Y服务器直接G.
这时我们如果我们引入生产者消费者模型,这样即使Q服务器的请求再怎么多,也不会影响到Y服务器 最坏的情况也就是Q服务器挂了 但Y服务器不会有任何影响 因为Q的请求都会放在阻塞队列中 , 有界 的阻塞队列就会引起阻塞 Y服务器 还是和原来一样处理这些请求 这样就起到了"削峰填谷"的作用.
这个模型还有一个优势就是高内聚低耦合 两个服务器不会直接接触 一个挂了不会对另一个产生影响.
用循环队列实现一个简单的阻塞队列 用于实现一个生产者消费者模型
第一种 就是生产者生产快 而消费者慢 就会出现 程序一运行生产者咔咔就会生产满了 接下来就会阻塞 消防者每消费一个 生产者才会生产一个. 下面我们通过代码来实现一下:
class MyBlockingQueue{private String[] elems = null;private volatile int head = 0;private volatile int tail = 0;private volatile int size = 0;public MyBlockingQueue(int capacity) {elems = new String[capacity];}void put(String elem) throws InterruptedException {//先加锁synchronized (this) {while (size >= elems.length) {//队列满了 ,就要等待this.wait();}//把新的元素放到tail所在的位置上elems[tail] = elem;tail++;if (tail >= elems.length) {//到达末尾 ,就回到开头tail = 0;}//更新size的值size++;//唤醒下面take的waitthis.notify();}}String take() throws InterruptedException {synchronized (this) {while (size == 0) {//为空要等待this.wait();}//取出 head指向的元素String result = elems[head];head++;if (head >= elems.length) {head = 0;}size--;//唤醒put上面的waitthis.notify();return result;}}
}public class Text2 {//实现一个阻塞队列 模拟一个生产者消费者模型public static void main(String[] args) {//实例化一个阻塞队列的对象 利用该阻塞队列实现一个生产者消费者模型MyBlockingQueue queue = new MyBlockingQueue(1000);//生产者线程Thread t1 = new Thread(() -> {try {int count = 0;while (true) {queue.put(count + "");System.out.println("生产:" + count);count++;}}catch (InterruptedException e) {e.printStackTrace();}});//消费者线程Thread t2 = new Thread(() ->{try {while (true) {String result = queue.take();System.out.println("消费:" + result );Thread.sleep(1000);}}catch (InterruptedException e) {e.printStackTrace();}});t1.start();t2.start();}
}
运行截图:
其实这里生产者和消费者的频率和快慢 我们都是可以通过代码来控制的 .
上面就是阻塞队列的应用场景 .
相信通过上诉内容 大家都了解并掌握阻塞队列了吧!
下一章内容 : 我们就会讲到线程池的有关内容 .
谢谢大家的浏览 !!!