如何实现一个阻塞队列
一.什么是阻塞队列
- 阻塞队列的作用是帮助我们更加方便地进行线程通信和协作1。阻塞队列区别于其他类型的队列的最主要的特点就是“阻塞”这两个字2。阻塞功能使得生产者和消费者两端的能力得以平衡,当有任何一端速度过快时,阻塞队列便会把过快的速度给降下来2。阻塞队列有三种用法:返回特殊值、一直阻塞和超时退出3。阻塞队列可以很好地缓冲线程池创建线程需要获取mainlock这个全局锁的影响,并且可以避免新任务的到达速率超过了线程池的处理速率4。
- 特点:
①阻塞队列是线程安全的
②.具有阻塞特性- 如果针对一个已经满了的阻塞队列做入队操作,就会产生阻塞.一直阻塞到队列不满(出队列)之后.
- 如果针对一个空了的阻塞队列做出队操作,就会产生阻塞,一直阻塞到队列不空(入队列)之后.
二.如何实现一个阻塞队列
基本步骤:
①实现一个普通队列:②保证线程安全:③.加上阻塞功能.
①实现一个普通队列
代码实现:
class MyBlockingQueue{
private String[] elems;
private int head = 0;
//表示队列的对头位置
private int tail = 0;
//表示队列的队尾位置
private int usedSize = 0;
//用于需要判断队列空或队列满的情况,表示队列中有几个元素.
public MyBlockingQueue(int capacity){
elems = new String[capacity];
}
//构造方法,并传参说明容量.
public void put(String elemt){
if(usedSize==elems.length){
//判断队列是否满了,如果满了应该阻塞等待
}
elems[tail] = elemt;
tail++;
if(tail>=elems.length){
tail = 0;
}
//如果tail到最后之后,需要把他重新放到0位置.
usedSize++;
}
//实现入队操作
public String take(){
if(usedSize == 0){
//判断是否为空,如果为空应该阻塞等待.
}
String ret = elems[head];
head++;
if(head>=elems.length){
head = 0;
}
//如果head到最后之后,需要把他重新放到0位置.
usedSize--;
return ret;
}
}
②.保证线程安全
代码实例:
class MyBlockingQueue{
private String[] elems;
private int head = 0;
//表示队列的对头位置
private int tail = 0;
//表示队列的队尾位置
private int usedSize = 0;
//用于需要判断队列空或队列满的情况,表示队列中有几个元素.
public MyBlockingQueue(int capacity){
elems = new String[capacity];
}
//构造方法,并传参说明容量.
synchronized public void put(String elemt){
if(usedSize==elems.length){
//判断队列是否满了,如果满了应该阻塞等待
}
elems[tail] = elemt;
tail++;
if(tail>=elems.length){
tail = 0;
}
//如果tail到最后之后,需要把他重新放到0位置.
usedSize++;
}
//实现入队操作
synchronized public String take(){
if(usedSize == 0){
//判断是否为空,如果为空应该阻塞等待.
}
String ret = elems[head];
head++;
if(head>=elems.length){
head = 0;
}
//如果head到最后之后,需要把他重新放到0位置.
usedSize--;
return ret;
}
}
此处的synchronized给方法加锁,等于给this加锁.也可以用synchronized给具体的代码加锁.
③.加上阻塞功能
代码实例:
class MyBlockingQueue{
private String[] elems;
private int head = 0;
//表示队列的对头位置
private int tail = 0;
//表示队列的队尾位置
private int usedSize = 0;
//用于需要判断队列空或队列满的情况,表示队列中有几个元素.
public MyBlockingQueue(int capacity){
elems = new String[capacity];
}
//构造方法,并传参说明容量.
synchronized public void put(String elemt) throws InterruptedException {
if(usedSize==elems.length){
//判断队列是否满了,如果满了应该阻塞等待
this.wait();
}
elems[tail] = elemt;
tail++;
if(tail>=elems.length){
tail = 0;
}
//如果tail到最后之后,需要把他重新放到0位置.
usedSize++;
this.notify();
//入队列之后,要唤醒由于队列空出队列时出现的阻塞等待.
}
//实现入队操作
synchronized public String take() throws InterruptedException {
if(usedSize == 0){
//判断是否为空,如果为空应该阻塞等待.
this.wait();
}
String ret = elems[head];
head++;
if(head>=elems.length){
head = 0;
}
//如果head到最后之后,需要把他重新放到0位置.
usedSize--;
this.notify();
//出队列之后,要唤醒由于队列满入队列时出现的阻塞等待.
return ret;
}
}
此时会出现一个问题:如果有两个线程一号线程和二号线程,同时入队列但队列为空,又有一个线程线程三,线程三入队列,入队列之后,队列不为空了,此时会随机唤醒一个因为线程空而正在阻塞的线程(即线程一或者线程二),如果唤醒的时线程一,那么线程一继续向下执行,成功入队列,此时线程一会唤醒线程二,但是,此时队列为空,代码就会出现线程安全问题
解决办法:将判断队列空满变为while循环.
代码实例:
class MyBlockingQueue{
private String[] elems;
private int head = 0;
//表示队列的对头位置
private int tail = 0;
//表示队列的队尾位置
private int usedSize = 0;
//用于需要判断队列空或队列满的情况,表示队列中有几个元素.
public MyBlockingQueue(int capacity){
elems = new String[capacity];
}
//构造方法,并传参说明容量.
synchronized public void put(String elemt) throws InterruptedException {
//入队列之后,要唤醒由于队列空出队列时出现的阻塞等待.
//判断队列是否满了,此处换成while,就意味着线程被唤醒之后,要再判定一次条件
//wait之前判定了一次,wait之后又判定了一次(相当于做了进一步判断)
while(usedSize==elems.length){
//判断队列是否满了,如果满了应该阻塞等待
this.wait();
}
elems[tail] = elemt;
tail++;
if(tail>=elems.length){
tail = 0;
}
//如果tail到最后之后,需要把他重新放到0位置.
usedSize++;
this.notify();
}
//实现入队操作
synchronized public String take() throws InterruptedException {
//判断队列是否满了,此处换成while,就意味着线程被唤醒之后,要再判定一次条件
//wait之前判定了一次,wait之后又判定了一次(相当于做了进一步判断)
while(usedSize == 0){
//判断是否为空,如果为空应该阻塞等待.
this.wait();
}
String ret = elems[head];
head++;
if(head>=elems.length){
head = 0;
}
//如果head到最后之后,需要把他重新放到0位置.
usedSize--;
this.notify();
//出队列之后,要唤醒由于队列满入队列时出现的阻塞等待.
return ret;
}
}
判断队列是否满了,此处换成while,就意味着线程被唤醒之后,要再判定一次条件
wait之前判定了一次,wait之后又判定了一次(相当于做了进一步判断)
总结
阻塞队列是基于普通队列实现的,与多线程联系到一起。主要是对wait()与notify()操作的应用.因此在Java标准库中,wait往往是和while在一起用的