队列与栈类似,基本操作是入队enqueue(),将数据放入队列的尾部,出队dequeue(),从队列头部取出一个元素;
所以,队列也是一种操作受限的线性表数据结构,队列应用广泛,例如循环队列、阻塞队列、并发队列,很多都偏向于底层系统、框架和中间件的开发;
高性能的Disruptor、Linux环形缓存,都用到了循环并发队列;Java concurrent并发包利用ArrayBlockingQueue来实现公平锁;
//用数组实现队列
public class ArrayQueue{
//数组:items.数组大小:n
private String[ ] items;
private int n=0;
//head表示头下标,tail表示尾下标
private int head = 0;
private int tail = 0;
//申请一个大小为capacity的数组
public ArrayQueue(int capacity){
items = new String[capacity];
n = capacity;
}
//入队
public boolean enqueue(String item){
if(tail==n) return false;
items[tail] = item;
++tail;
return true;
}
//出队
public boolean dequeue(String item){
if(head==tail) return null;
String ret = items[head];
++head;
return ret;
}
}
由于入队、出队操作,head和tail都会往后移,当tail移到最右边的时候,即便是数组中还有空闲空间,也无法继续往队列中添加数据,改造一下刚才的入队函数enqueue(),代码如下:
//入队操作
public boolean enqueue(String item){
if(tail==n){
//tail=n&&head=0,表示整个队列已经满了
if(head==0) return false;
//数据搬移
for(int i=head;i<tail;++i)
{
iems[i-head] = items[i];
}
//搬移完数据之后,更新tail和head
tail-=head;
head = 0;
}
items[tail] = item;
++tail;
return true;
}
如下图所示
采用循环队列很好的解决了数据搬移的问题,但是循环队列代码实现的关键是队空和队满的判定条件;
当队满时,(tail+1)%n=head,队列满时,tail指向的位置实际上是没有存储数据的,所以循环队列会浪费一个数组的存储空间。
代码如下:
public class CircularQueue{
//数组:items,数组大小:n
private String[ ] items;
private int n = 0;
//head和tail指针
private int head = 0;
private int tail = 0;
public CircularQueue(int capacity){
items = new String[capacity];
n = capacity;
}
//入队
public boolean enqueue(String item){
//队满的判定条件
if((tail+1)%n==head) return false;
items[tail] = item;
tail = (tail+1)%n;
return true;
}
//出队
public String dequeue(){
if(head==tai) return null;
String ret = items[head];
head = (head+1)%n;
return ret;
}
}
循环队列能否写好的关键就是,队满和队空的判定条件;
阻塞队列就是在对立的基础上增加了阻塞操作,当队列为空时,从对头取出数据会被阻塞;当队满的时候,插入数据的操作也会被阻塞,直到队列中有空闲位置以后,再插入数据;
类似于生产者-消费者模型,来有效的协调生产和消费的速度。当“生产者”生产的数据过快时,消费者来不及消费,存储数据的队列就满了,此时的生产者就会阻塞等待,直到消费者消费了数据,生产者才会被继续唤醒,为了提高数据的处理效率,可以多配置几个消费者来应对一个生产者。
当多个线程操作一个队列时,就会出现线程安全问题,这个时候就需要并发队列,后面我学到了会更新,先占个坑!
当线程池没有空闲线程时,新的任务请求线程资源时,线程池该如何处理?
那么如何公平的处理,每个排队的请求呢?先进者先服务?