阻塞队列
阻塞队列是基于普通队列做出的扩展(先进先出)
1.线程安全的
2.具有阻塞特性
a)如果针对一个已经满了的队列进行入队列,那么入队操作就会阻塞,一直阻塞到队列不满(其他线程出队列元素)之后
b)如果针对一个已经空了的队列进行出兑列,此时出队列操作就会阻塞,一直阻塞到队列不空(其他线程引入元素)之后
基于阻塞队列,就可以实现"生产者消费者模型"---------------------->描述的是一种多线程编程的思想
生产者消费者模型
1.引入生产者消费者模型,我们可以做到更好的"解耦和"(把代码的耦合程度从高降低~~就称为哦和解耦合)
实际开发中,经常会涉及到:分布式系统,服务器整个功能并不是由一个服务器全部完成的,而是每个服务器负责一部分功能,通过服务器之间的网络通信,最终完成整个功能~~
上述模型中,A和B,A和C之间的耦合程度较高,
A中的代码涉及B和C的一些相关操作,B和C也设计A的一些操作,
此时,如果B或C挂了,对A的影响就很大了,A很有可能就也挂了,此时,如果我们想要再增加一个D服务器功能,就需要在A的基础上再做修改,这样就会越改越乱,那么此时,我们就可以引入生产者消费者模型来解决这个问题,通过引入阻塞队列,构建生产者消费者模型来去解决这个问题.
此时,A和B,A和C之间就不用直接进行交互了,而是通过阻塞队列进行交互,A不知道B和C的存在,B和C也不知道A的存在,它们只需要跟阻塞队列进行交互就可以.
这种情况下,如果B和C挂了,对A几乎没有影响.
如果后续需要增加一个服务器D,A的代码也几乎不需要做改变.
使用这种模式需要加入更多的机器,
一:引入更多的硬件资源,
1)上述描述的阻塞队列,并不是简单的数据结构,而是基于这个数据结构实现的服务器程序,又被部署到单独的主机上去了.(消息队列(message queue,mq))
2)整个系统的结构更复杂了,你要维护的服务器更多了.
3)效率,引入中间的这个阻塞队列,请求从A出发发送给B,这个过程需要经过阻塞队列的转发,这个过程肯定也是有一定的开销的.
二:削峰填谷
我们的服务器在处理每个请求的时候,都是需要消耗硬件资源的,包括但不限于,CPU,内存,硬盘,网络带宽.
在请求激增这种情况下,即使一个请求消耗的资源很少,但是如果同时有很多请求的话,加到一起,消耗的硬件资源就多了.上述任何一种硬件资源达到瓶颈的话~~服务器就会挂,(服务器不会返回响应了)
图中的阻塞队列没有什么业务逻辑,只是用来存储数据,抗压能力比较强,即使外界的请求出现峰值,也是由队列来承担峰值请求,B和C还是按照之前的速度来进行读取.
阻塞队列具体实现
ArrayBlockingQueue
LinkedBlockingQueu
PriorityBlockingQueue
这些类都实现了BlockingQueue这个接口interface
该队列具有put和offer两个入栈,但是put带有阻塞,offer没带阻塞(队列满了会返回结果)
阻塞队列没有提供带有阻塞功能的获取队首元素的方法.
1)普通队列
class MyBLOCKEDQUEUE{
private String elems[]=null;//队列存储结构
private int size=0;//当前元素数量
private int tail=0;//队尾
private int head=0;//队头
public MyBLOCKEDQUEUE(int size){
this.elems=new String[size];
}
public void put(String elem){
elems[tail]=elem;
size++;
tail++;
if(tail>=elems.length){
tail=0;
}
}
public String take(){
String ret=elems[head];
head++;
size--;
if(head>=elems.length){
head=0;
}
return ret;
}
}
2)线程安全
为了避免这种情况,在一个put还没有实现tail++操作的时候,另外一个线程已经进行put了,那么这样就会出现线程安全,take也是同理,所以,我们要给他加上锁.
3)阻塞功能
我们在入队列的时候进行判断,如果队列满了,就需要进入阻塞,等到队列出元素为止,
出队列的时候也要进行判断,如果队列为空,就需要在为空的时候,等到队列有元素进入停止等待.
但是,还有一种情况需要考虑,
这样就会进行错误唤醒
最终代码:
class MyBLOCKEDQUEUE{
private String elems[]=null;//队列存储结构
private int size=0;//当前元素数量
private int tail=0;//队尾
private int head=0;//队头
private Object locker=new Object();
public MyBLOCKEDQUEUE(int size){
this.elems=new String[size];
}
public void put(String elem) throws InterruptedException {
synchronized (locker) {
while (size>= elems.length){//被唤醒之后需要再次进行判断,看是否满足条件,防止被错误唤醒
locker.wait();
}
elems[tail] = elem;
size++;
tail++;
if (tail >= elems.length) {
tail = 0;
}
}
locker.notify();
}
public String take() throws InterruptedException {
synchronized (locker) {
while (size==0){
locker.wait();
}
String ret = elems[head];
head++;
size--;
if (head >= elems.length) {
head = 0;
}
locker.notify();
return ret;
}
}
}
一般wait都是搭配while循环来进行使用的
我们可以使用阻塞队列来实现基本的一个生产者消费者模型
class MyBlockingQueue{
private String []elem=null;
private int size;
private int head;
private int tail;
private Object locker=new Object();
public MyBlockingQueue(int size){
elem=new String[size];
this.size=0;
}
public void put(String s) throws InterruptedException {
synchronized (locker){
while(size>=elem.length){
locker.wait();
//进入阻塞状态
}
elem[tail]=s;
size++;
tail++;
if(tail>=elem.length){
tail=0;
}
locker.notify();
}
}
public String take() throws InterruptedException {
String ret;
synchronized (locker){
while(size<=0){//有可能被
locker.wait();
//进入阻塞状态
}
ret=elem[head];
head++;
size--;
if(head>=elem.length){
head=0;
}
locker.notify();
}
return ret;
}
}
public class ThreadDemon24 {
public static void main(String[] args) {
MyBlockingQueue queue=new MyBlockingQueue(100);
Thread t1=new Thread(()->{
int count=0;
while(true){
try {
queue.put(""+count);
System.out.println("生产商品"+count);
count++;
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
Thread t2=new Thread(()->{
while(true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
try {
String t=queue.take();
System.out.println("消费商品"+t);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t1.start();
t2.start();
}
}
部分运行结果
实际开发中,生产者消费者往往是多个生产者消费者,这里的生产者和消费者往往不仅仅是一个线程,也可能是一个独立的服务器程序,甚至是一组服务器程序.
在这值周,最核心的部分依旧是阻塞队列,(使用synchronized和wait/notify)达到线程安全&阻塞