算法与数据结构之美-队列

队列在线程池等有限资源池中的应用

  • 如何理解“队列”?
  • 顺序队列和链式队列
    • 顺序队列
    • 链式队列
    • 循环队列
  • 阻塞队列和并发队列
  • 解答开篇

CPU的资源有限,任务的处理速度与线程个数不是正相关。但是过多的线程会导致CPU切换频繁,使得处理性能下降。因此,线程池的大小一般要考虑处理任务的特点和硬件条件,来事先设定。

如何理解“队列”?

队列与栈类似,基本操作是入队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;
}

链式队列

  • 基于链表的实现,同样需要设定head和tail两个指针,分别指向头结点和尾结点
    算法与数据结构之美-队列_第1张图片

循环队列

如下图所示
算法与数据结构之美-队列_第2张图片
采用循环队列很好的解决了数据搬移的问题,但是循环队列代码实现的关键是队空和队满的判定条件;
当队满时,(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;
}
}

循环队列能否写好的关键就是,队满和队空的判定条件;

阻塞队列和并发队列

阻塞队列就是在对立的基础上增加了阻塞操作,当队列为空时,从对头取出数据会被阻塞;当队满的时候,插入数据的操作也会被阻塞,直到队列中有空闲位置以后,再插入数据;
算法与数据结构之美-队列_第3张图片
类似于生产者-消费者模型,来有效的协调生产和消费的速度。当“生产者”生产的数据过快时,消费者来不及消费,存储数据的队列就满了,此时的生产者就会阻塞等待,直到消费者消费了数据,生产者才会被继续唤醒,为了提高数据的处理效率,可以多配置几个消费者来应对一个生产者。
算法与数据结构之美-队列_第4张图片
当多个线程操作一个队列时,就会出现线程安全问题,这个时候就需要并发队列,后面我学到了会更新,先占个坑!

解答开篇

当线程池没有空闲线程时,新的任务请求线程资源时,线程池该如何处理?

  • 非阻塞处理方式,直接拒绝任务请求;
  • 阻塞处理方式,请求排队,等到有空闲线程时,取出队列的请求继续处理;

那么如何公平的处理,每个排队的请求呢?先进者先服务?

  • 基于链表的实现方式,支持无限排队的无界队列(unbounded queue),但是可能会导致过多的排队请求,请求处理的响应时间过长,针对于队时间敏感的系统是不合适的;
  • 基于数组实现的有界队列,队列的大小有限,当线程池中排队请求超过队列大小时,接下来的请求就会被拒绝,这种方式更加合理,但是需要设定一个合理的队列大小;队列太大导致等待的请求太多,队列太小就会导致无法充分利用系统资源、发挥最大性能;

你可能感兴趣的:(数据结构与算法之美)