专栏:JavaEE初阶
标题:阻塞队列 ------ Java
阻塞队列 ------Java
- ==引言==
- ==优先级队列==
- ==阻塞队列==
-
- [`一.特性(具有阻塞特性)`](#
一.特性(具有阻塞特性)) - `二.引用场景`
- `生产者消费模型`
- `生产者消费模型的优点`
- ==一.解耦合==
- `二.削峰填谷`
- [`一.特性(具有阻塞特性)`](#
- ==阻塞队列相关代码的使用==
引言
阻塞队列是线程安全的队列;为什么这么说呢???下面我们就来走进我们的阻塞队列的世界,但是在学习之前:我们首先进行回忆一下之前学过的优先级队列,方便我们更好的学习阻塞队列的优势,接着我们进行学习阻塞队列的应用场景;最后我们进行学习阻塞队列,以及阻塞队列的代码实现;
优先级队列
阻塞队列
在进行学习阻塞队列前,我们必须要明确这几点: 1.阻塞队列是比优先级队列更复杂的一种队列; 2.阻塞队列被运用到多线程的情况下;
一.特性(具有阻塞特性)
1.当队列为空的时候,进行出队列的操作就会被阻塞,阻塞到其它线程向队列添加进元素为止;
2.当队列放慢的时候,进行入队列的操作就会被阻塞,阻塞到其它线程进行出队例操作后为止;
通过线程特性的描述我们就可以确定了阻塞队列就是线程安全的;
二.引用场景实现了生产者消费模型
生产者消费模型
关于生产者消费模型,我将一生活案例进行讲解,方便我们进行更好的理解;
包饺子::每到过年的时候:我们家我爸爸,妈妈,奶奶三个人进行包饺子;在我上小学的时候:他们三个人的包饺子的策略是:每个独立进行完成一个饺子的过程,就比如:我爸包饺子会用到擀面杖擀饺子皮,完后进行包饺子,完后把饺子放到篮子里;我妈妈,奶奶也都是同样的方式;由于我家里只用一根擀面杖,这样就导致了其中一个人在擀饺子皮的时候另外两个人只能进行等待,这就会产生阻塞等待;很是影响包饺子的效率;
在我上初中的时候,我爸妈,奶奶给变了包饺子策略:变成了我爸负责擀饺子皮,我妈妈和我奶奶负责进行包饺子,并且我爸擀饺子的速度差不多正好能供应上我妈妈和我奶奶;我爸在每次擀好的饺子皮就放到篮子里;我妈妈和我奶奶进行从篮子里去走饺子皮进行包饺子;这样就大大提高了包饺子的效率;
就像在这种策略完成完成包饺子的过程中:
我爸就属于:"生产者",我妈妈和我奶奶就属于"消费者",而进行传递饺子皮的篮子就属于"生产者与消费者的交易场所";
-通过这个例子的了解我们就差不多明确了什么是生产者消费模型了;呢么这和我们学习阻塞队列又有什么关系呢?????
阻塞队列的作用就是基于生产者消费模型的而进行发挥的;那又为什么这么说呢??????
还是接着上面的案例:我爸肯定会在那一时刻会赶不上我奶奶和我妈包饺子的速度;毕竟我爸是一个人;
在赶不上的时候,现实生活中,我妈妈和我奶奶奶会明确的知道就等一会,但是电脑就不知道了;这样就会导致其中一别会一直等待另一边进行传信息,在等待的过程中也会一直占用着资源,这样就影响效率,并且大大消耗成本,也就是在做无用功;这样我们就想到了阻塞队列作为中间桥梁进行起来两边的交互;
这样阻塞队列就相当于阻塞队列就是那个"传递饺子皮的篮子;
生产者消费模型的优点一.解耦合
在我前面的文章中我介绍了什么是解耦合以及内聚的知识;关于解耦合方面它不仅可以运用在两个线程之间,而且可以运用在服务器方面;
下面我就以关于阻塞队列作为中间桥梁在服务器方面关于生产者消费模型的实现进行讲解;

关于上图的情况下:服务器A和B直接进行交互,这样作为程序员的我们在进行代码A实现的时候,为了更好的和B进行交互,就会涉及到B的相关代码逻辑设置;这样
A和B的耦合度就很高;在AB两者每个遇到情况下,都会影响到各自情况;
在这种情况下,我们为了方便后期成本的维护,这样我们就引进了生产者消费模型进行解耦合;
生产者模型的应用
-
在一些情况下,阻塞队列很重要,有时会把阻塞队列单独部署成一个服务器:称为"消息队列"(包含多个阻塞队列);
二.削峰填谷
我们首先以用波峰图体现服务器的请求量来进行考虑出现的相关的情况;
---
在上述的情况下,我们就会考虑到如何缓冲波峰的压力呢,如果可以解决波峰的压力,这样就可以解决问题;于是,我们在这样的背景下:就提出了关与阻塞队列的引用来实现生产者消费模型;
生产者消费模型的应用
通过上面的学习,我们对生产者消费模型也有了一定的了解,下面我们利用两个线程模拟使用生产者消费模型进行实践;
java
public static void main(String[] args) throws InterruptedException {
BlockingDeque<Integer> blockingDeque=new LinkedBlockingDeque<>(20);
Thread t1=new Thread(()->{
int n=0;
while(true){
try {
//Thread.sleep(1000);
blockingDeque.put(n);
System.out.println("生产元素"+n);
n++;
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"producer");
Thread t2=new Thread(()->{
while (true){
try{
Thread.sleep(1000);
Integer n=blockingDeque.take();
System.out.println("消费者"+n);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"consumer");
t1.start();
t2.start();
}
代码解释
//创建两个线程,,完成"生产者与消费者模型"
//第一个线程为生产者线程,另一个线程为消费者线程;
//通过阻塞队列的添加和删除元素来完成该功能;进一步体会消息队列的阻塞功能;
//观察结果发现:完成了关于生产者与消费者模型;
//但是很难观察出消息队列的阻塞功能;
//因此:设置一个线程休眠功能;
// ①设置在生产者进程下:会出现:消费者消耗>生产者生产;
// 就会出现生产者生产一个,消费者消耗一个;
// ②设置在消费者进程下:会出现:生产者猛烈生产>消费者的消耗;
//会出现:生产者将消息队列填满,后续消费者消耗一个,生产者生产一个;
//此处提供了方法①的使用;
阻塞队列相关代码的使用
在Java中也提供了一个关于阻塞队列的类:
BlockingQueue
`阻塞队列的两个主要方法的使用:
①:入队列操作;
②:出队列操作
javapublic static void main(String[] args) throws InterruptedException { BlockingDeque blockingDeque=new LinkedBlockingDeque(); blockingDeque.put("aaa");//向阻塞队列中添加元素; Object ret=blockingDeque.take();//从阻塞队列中进行获取元素; System.out.println(ret); }注意情况:
阻塞队列可以进行设置最大容量
通过上面阻塞队列的应用场景和阻塞队列的代码使用,下面我们进行自己实现一个基于生产者消费模型的阻塞队列;
下面我首先进行模拟实现阻塞队列的两个操作方法;在后续操作中模拟实现阻塞队列的更多功能;这样就可以让我们更好的学会阻塞队列;循序渐进才是成功的良药;'
阻塞队列基础版
java
>//模拟实现一个消息队列,完成put和take方法的实现;
class MyBlockQueue{
private String[] date=null;
private int head=0;//队头;
private int tail=0;//队尾;
int size=0;//实际元素的个数;
//创建构造方法;
public MyBlockQueue(int capcity){
date=new String[capcity];
}
//实现向消息队列中添加元素;
public void put(String elem) {
synchronized (this) {
if (size >= date.length) {
//队列空了,需要进行阻塞;
return;
}
date[tail]=elem;
tail++;
if(tail>= date.length){
tail=0;
}
size++;
}
}
//删除消息队列中的元素;
public String take(){
synchronized (this) {
if (size == 0) {
//此时说明队列为,要进行阻塞等待了;
return null;
}
String ret=date[head];
head++;
if(head>=date.length){
head=0;
}
size--;
return ret;
}
// return ret;
}
}
public class Demo31 {
public static void main(String[] args){
MyBlockQueue myBlockQueue=new MyBlockQueue(20);
Thread t1=new Thread(()->{
int n=0;
for(int i=0;i<20;i++){
System.out.println("生产者进行生产元素" + n);
myBlockQueue.put(n + "");
n++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
Thread t2=new Thread(()->{
String n=null;
for(int i=0;i<20;i++) {
n = myBlockQueue.take();
System.out.println("消费者进行获取元素" + n);
}
});
t1.start();
t2.start();
}
}
上面这段代码,我只是进行了阻塞队列的两个操作的模拟实现,以及测试了一下两个代码的正确性;
由于我们前面学到的阻塞队列是线程安全的,所以接下来引进wait和notify方法进行实现阻塞队列的线程安全性以及对应的阻塞功能;
阻塞队列线程安全版
java
/阻塞队列的模拟实现------升级版;
//首先模式阻塞队列的容器为一个循环数组;
//其次模拟实现阻塞队列的添加元素和删除元素操作;
class MyBlockedQueuePlass{
private String[] date=null;
private int head=0;//头位置标记位;
private int tail=0;//尾位置标记位;
private int size=0;//数组容器有效元素的个数;
//方法一:构造容器;
public MyBlockedQueuePlass(int capacity){
date=new String[capacity];
}
//方法二:向阻塞队列中添加元素;
public void put(String elem) throws InterruptedException {
synchronized (this){
while(size>= date.length){
this.wait();
}
date[tail]=elem;
tail++;
if(tail>=date.length){
tail=0;
}
size++;
this.notify();
}
}
//方法三:删除阻塞队列中的元素;
public String take() throws InterruptedException {
synchronized(this) {
while (size == 0) {
this.wait();
}
String ret=date[head];
head++;
if(head>= date.length){
head=0;
}
size--;
this.notify();
return ret;
}
}
}
public class Demo31_1 {
public static void main(String[] args){
MyBlockedQueuePlass myBlockedQueuePlass=new MyBlockedQueuePlass(1000);
Thread producer=new Thread(()->{
int n=0;
for(int i=0;i<500;i++){
try{
myBlockedQueuePlass.put(n+"");
System.out.println("生产者进行生产元素"+n);
n++;
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
Thread consumer=new Thread(()->{
String n=null;
for(int i=0;i<500;i++){
try {
n = myBlockedQueuePlass.take();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("消费者进行消费元素"+n);
}
});
producer.start();
consumer.start();
}
}
代码关键点设置图解;





