多线程--阻塞队列
目录
一、阻塞队列
1.阻塞队列是什么?【查看队列相关知识】
2、如何使用阻塞队列
二、生产者消费者模型
1.什么是生产者消费者模型
2.生产者消费者模型是用来做什么的?
3.阻塞队列 结合 生产者消费者模型
三、模拟实现阻塞队列
总结
前言
小亭子正在努力的学习编程,接下来将开启javaEE的学习~~
分享的文章都是学习的笔记和感悟,如有不妥之处希望大佬们批评指正~~
同时如果本文对你有帮助的话,烦请点赞关注支持一波, 感激不尽~~
一、阻塞队列
1.阻塞队列是什么?【查看队列相关知识】
阻塞队列 是一种带有阻塞功能的, 线程安全的队列, 也遵守 “先进先出” 原则,线程是安全的。
如果队列为空, 则不出队, 进入堵塞状态, 等其他线程插入数据, 队列不为空时再出队
如果队列为满, 则不入队, 进入堵塞状态, 等其他线程删除数据, 队列不为满时再入队
2、如何使用阻塞队列
Java集合中封装了阻塞队列这种数据结构, 即: BlockingQueue 接口, 其具体实现类即有顺序存储形式, 也有链式存储形式。
补充:
JDK7提供了7个阻塞队列。分别是
ArrayBlockingQueue : 一个由数组结构组成的有界阻塞队列。
LinkedBlockingQueue : 一个由链表结构组成的有界阻塞队列。
PriorityBlockingQueue : 一个支持优先级排序的无界阻塞队列。
DelayQueue: 一个使用优先级队列实现的无界阻塞队列。
SynchronousQueue: 一个不存储元素的阻塞队列。
LinkedTransferQueue: 一个由链表结构组成的无界阻塞队列。
LinkedBlockingDeque: 一个由链表结构组成的双向阻塞队列。``
举个栗子:
现在我们来实现一个顺序存储的阻塞队列
约定容量是5,入队操作为put,出队操作为take。插入5个元素以后,队列空间满了,无法再插入,发生了阻塞。
二、生产者消费者模型
1.什么是生产者消费者模型
生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取。
【生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。】
举个栗子:
A 负责产出, B 负责消耗, A 和 B 不直接通讯, 而是通过一个平台进行通讯 例如, 小明和小红要包饺子~ 小明负责擀饺子皮, 擀好的饺子皮放在案板上, 小红负责包饺子 , 那么小明就是生产者, 小红就是消费者, 案板就是进行通讯的平台
这就可以理解为一个生产者消费者模型
2.生产者消费者模型是用来做什么的?
1) 阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力.(削峰填谷)
这个缓冲区就像三峡大坝,雨季的时候蓄水,旱季的时候放水
【举个栗子:
A,B两个服务器,A的性能很好,平时可以同时接收1W的请求,突然有一时刻收到了1.1W的请求,因为性能好,勉强可以承受住,但是B的性能一般,最多最多能承受1W,如果AB直接相连的话B也会收到1.1W的请求,B很有可能崩了,但是,当有一个缓冲区——阻塞队列服务器C,A在收到请求后传给C,C先存着,按照B能接受的速度传给B,这样B就不那么容易崩了。
】
2) 阻塞队列也能使生产者和消费者之间 解耦合
【简单补充一下:
耦合:模块与模块之间的联系
内聚:模块内部之间的联系
高内聚,低耦合:举个栗子:你媳妇生气了,不搭理你,你一天都焦头烂额的,因为你两关系亲密你们是一家人(内聚)。但是如果是你的一个普通同学不搭理你,对你就没啥影响,因为你两“不熟”,你们不是一家人(耦合)。】
3.阻塞队列 结合 生产者消费者模型
创建两个线程, thread1 作为消费者, thread2 作为生产者
消费者负责在阻塞队列中删除数据, 生产者负责在阻塞队列中增加数据
每次生产和消费之后休眠一秒, 并进行打印, 方便观察执行情况
public static void main(String[] args) {
BlockingQueue<Integer> queue = new LinkedBlockingDeque<>();
//生产者
Thread thread1 = new Thread(()->{
int value = 0;
while (true) {
try {
queue.put(value);
Thread.sleep(1000); //让生产者休眠,制造出生产<消费的情况
System.out.println("加入:" + value);
value++;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//消费者
Thread thread2 = new Thread(()->{
int value = 0;
while (true) {
try {
queue.take();
System.out.println("删除:" + value);
value++;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread1.start();
thread2.start();
}
三、模拟实现阻塞队列
**实现阻塞队列需要以下三步:
- 实现一个队列(这里采用顺序存储的环形队列)
- 保证线程安全
- 实现阻塞效果**
环形队列代码实现:(没有用泛型)
static class MyBlockingQueue {
private int[] items = new int[1000];//用数组实现队列
volatile private int head = 0; // volatile防止指令重排序等
volatile private int tail = 0;
volatile private int size = 0;
//加锁
synchronized public void put(int elem) throws InterruptedException {
while (size == items.length) { //队满
this.wait(); //阻塞等待
}
items[tail] = elem; //入队
tail++;
if (tail == items.length){
//tail回到0,实现循环队列
tail = 0;
}
size++;
this.notify();//唤醒
}
//加锁
synchronized public Object take() throws InterruptedException {
while(size == 0) { //队空
this.wait(); //zs等待
}
int value= items[head]; // 出队
head++;
if (head == items.length){
//实现循环
head = 0;
}
size--;
this.notify();//唤醒
return value;
}
}
public static void main(String[] args) {
MyBlockingQueue queue = new MyBlockingQueue();
//消费者
Thread thread1 = new Thread(()->{
int value = 0;
while (true) {
try {
value = (int) queue.take();
System.out.println("消费:" + value);
value++;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//生产者
Thread thread2 = new Thread(()->{
int value = 0;
while (true) {
try {
queue.put(value);
System.out.println("生产:" + value);
value++;
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread1.start();
thread2.start();
}
说明:
synchronized 修饰,保证了修改操作的原子性, put和take方法都有修改数据的操作, 所以这两个方法都直接用synchronized加锁 。
读操作要保证满足内存可见性, 所以 size, front 和 rear都加上 volatile 修饰
加上阻塞效果,所以在 put 和 take 方法中的判空, 判满处使用wait 方法, 并且在满足条件时用notify方法唤醒。wait方法是可以被外部的 interrupt 方法打断的, 而不是被 notify 唤醒, 此时代码就可能就破坏了阻塞特性, 所以要把 if 换成 while , 如果不是被 notify 唤醒, 就再判断一下是否满足非空 / 非满这个条件
注意:
wait方法是可能被外部的 interrupt 方法打断的, 而不是被 notify 唤醒, 此时代码就可能就破坏了阻塞特性, 所以要使用while , 如果不是被 notify 唤醒, 就再判断一下是否满足非空 / 非满这个条件
总结
以上就是本篇要分享的内容,主要讲了阻塞队列,消费者生产者模型,二者结合的实现。
还没有评论,来说两句吧...