阻塞队列,生产者消费者模型
目标:
1. 认识与使用阻塞队列
2. 认识与实现消费者模型
目录
阻塞队列的特点
生产者消费者模型
生产者消费者模型的优点
阻塞队列实现该模型
阻塞队列的特点
1. 线程安全
2. 带有阻塞特性
(1)如果队列为空,继续出队列,就会发生阻塞。直到其他线程往队列里添加元素
(2)如果队列为满,继续入队列,就会发生阻塞。直到其他线程从队列里取走元素
java库的阻塞队列
1. BlockingDeque为抽象类,不能直接new,java库中提供了 数组 和 链表的方式实现
2. 阻塞队列继承与Queue,要是想实现阻塞特性使用 put() 和 take() 方法
生产者消费者模型
以包饺子为例子,流程一般为:和面,擀饺子皮,包饺子。我擀饺子皮,另外的人负责包饺子消耗饺子。这种工作模式就是生产者-消费者模型。
但是我生产的饺子皮放到哪呢?一般是放到 盖帘 上(就相当于阻塞队列)。我擀饺子皮慢的话,另外的人就得等着我擀好,再包饺子。(从空的队列中获取元素时阻塞);我擀饺子皮快的话, 盖帘上 满了 ,我就得等待(往满的队列里添加元素也会堵塞)。
生产者消费者模型的优点
1. 解耦合
两个模块,联系越紧密,耦合就越高。
比如实现一个简单的分布式系统:
客户端向A(服务器)发送请求,但是这个请求也得从A传到B,B再返回给A才能返回响应到客 户,这下A和B有着高耦合(B出现问题可能对A会有影响)。
相比之下,使用生产者消费者模型,在A和B之间封装一个阻塞队列,让A传给这个阻塞队列, 再传给B,这下就降低了A与B的耦合(B出现问题对A没有影响)。
2. 削峰填谷
峰:短时间内请求量多的
谷:短时间内请求量比较少
以上一个为例子,如果客户端传给A的数据比较多,则B也就会有很多数据。(可能会出现问 题:B不能直接承受这么多的数据)
先比之下, 使用生产者消费者模型,在A和B之间封装一个阻塞队列,让A传给这个阻塞队列,
再传给B,在B所能承受的范围内,多余的先存放到队列中,这下B就不会挂了。
阻塞队列实现该模型
在这里生产者和消费者我们使用两个线程来表示。
1. 生产者生产的慢,消费的快
public class Tset {public static void main(String[] args) {BlockingDeque<String> queue = new LinkedBlockingDeque<>(1000);//生产者Thread t1 = new Thread(() -> {int num = 1;while (true) {try {queue.put(num + "");System.out.println("生产元素:" + num++);Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}}});//消费者Thread t2 = new Thread(() -> {while (true) {try {String result = queue.take();System.out.println("消费元素" + result);} catch (InterruptedException e) {throw new RuntimeException(e);}}});t1.start();t2.start();}
}
2. 生产者生产的快,消费的慢
public class Tset {public static void main(String[] args) {BlockingDeque<String> queue = new LinkedBlockingDeque<>(1000);//生产者Thread t1 = new Thread(() -> {int num = 1;while (true) {try {queue.put(num + "");System.out.println("生产元素:" + num++);} catch (InterruptedException e) {throw new RuntimeException(e);}}});//消费者Thread t2 = new Thread(() -> {while (true) {try {String result = queue.take();System.out.println("消费元素" + result);Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}}});t1.start();t2.start();}
}