我们都知道,CPU资源是有限的,任务的处理速度与线程个数并不是线性正相关。相反,过多的线程反而会导致CPU频繁切换,处理性能下降。所以线程池的大小一般都是综合考虑要处理任务的特点和硬件环境,来事先设置的。
当我们向固定大小的线程池中请求一个线程时,如果线程池中没有空闲资源了,这个时候线程池如何处理这个请求?是拒绝请求还是排队请求?各种处理策略又是怎么实现的呢?
实际上,这些问题并不复杂,其底层的数据结构就是我们今天要了解的内容,队列(queue)。
1.什么是队列?
可以把队列想象成排队买票,先来的先买,后来的人只能站末尾,不允许插队。先进者先出,这就是典型的“队列”。
栈只支持两个基本操作:入栈push()和出栈pop()。队列和栈非常相似,支持的操作也很有限,最基本的操作也是两个:入队enqueue(),放一个数据到队列尾部;出队dequeue(),从队列头部取一个元素。
所以,队列和栈一样,也是一种操作受限的线性表数据结构。
2.顺序队列和链式队列
跟栈一样,队列可以用数组实现,也可以用链表来实现。用数组实现的队列叫做顺序队列,用链表实现的队列叫做链式队列。
3.循环队列
顾名思义,循环队列长得有点像一个环。原本数组是有头有尾的,是一条直线。现在,我们把首尾相连,扳成一个环。
假如一个队列大小为8,当前队头指针head=4,队尾指针tail=7,。当又有一个新的元素a入队时,我们放入下标为7的位置,但是此时,tail不会更新为8,而是会在环中后移一位,移到下标为0的位置。通过这样的操作,就可以避免顺序队列进行操作会遇到的数据搬移操作。但是循环队列的实现比较难,需要确定好队空和队满的判定条件。
在顺序队列中,队满的判断条件是tail=n,队空的判断条件是head=tail。那么,针对循环队列的话,如何判断队空和队满呢?
队空的判断条件仍然是head=tail,但是队满的判断稍微有点复杂。队满的判断条件是(tail+1)%n=head,当队满的时候,tail指向的位置是没有存储数据的,因此,循环队列会浪费一个数组的存储空间。
4.阻塞队列和并发队列
阻塞队列其实就是在队列基础上增加了阻塞操作。在队列为空的时候,从队头取数据就会被阻塞。因为此时还没有数据可取,直到队列中有了数据才能返回;如果队列满了,那插入数据的操作就会被阻塞,直到队列中有空闲位置再插入数据,然后再返回。
上述定义其实就是一个“生产者-消费者模型”。可以使用阻塞队列实现一个“生产者-消费者模型”。
基于阻塞队列实现的“生产者-消费者模型”,可以有效地协调生产和消费的速度。也可以通过协调“生产者”和“消费者”的个数,来提高数据的处理效率。
在多线程情况下,会有多个线程同时操作队列,这个时候,就会存在线程安全问题,如何实现一个线程安全的队列呢?
线程安全的队列叫做并发队列。最简单直接的实现方式是直接在enqueque()、dequeque()发布方法上加锁,但是锁粒度大并发度会比较低,同一时刻仅允许一个存或取操作。实际上,基于数组的循环队列,利用CAS原子操作,可以实现非常高效的并发队列。这也是循环队列比链式队列应用更加广泛的原因。
5.解答开篇
线程池没有空闲线程时,新的任务请求线程资源时,线程池该如何处理?各种策略又是如何实现的呢?
一般有两种处理策略。第一种是非阻塞的处理方式,直接拒绝任务请求;另一种是阻塞的处理方式,将请求排队,等到有空闲线程时,取出排队的请求去继续处理。那么,如何处理排队的请求呢?
我们希望公平地处理每个排队的请求,先进者先服务,所以队列这种数据结构就很适合来存储排队的请求。
基于链表的链式队列,可以实现一个支持无限排队的无界队列,但是可能会导致过多的请求排队等待,请求处理的响应时间过长。所以,针对响应时间比较敏感的系统,基于链表实现的无限排队的线程池是不合适的。
而基于数组实现的有界队列,队列的大小有限,所以线程池中排队的请求超过队列大小时,接下来的请求就会被拒绝,这种方式对响应时间敏感的系统来说,就相对更加合理。不过,设置一个合理的队列大小,也是非常讲究的。队列太大导致等待的请求太多,队列太小会导致无法充分利用系统资源,发挥最大性能。
除此之外,队列可以应用在任何有限资源池中,用于排队请求,比如数据库连接池等。实际上,对于大部分资源有限的场景,当没有空闲资源时,基本都可以通过“队列”这种数据结构来实现请求排队。
6.内容小结
队列最大的特点就是先进先出,主要的操作是入队和出队。既可以用数组实现,也可以用链表实现。用数组实现的是顺序队列,用链表实现的叫链式队列。还有一种像环一样的循环队列。在数组实现队列的时候,会有数据搬移的操作,要想解决数据搬移的问题,我们就需要像环一样的循环队列。
循环队列的实现关键要确定好队空和队满的判定条件。
除此之外,还有几种高级队列,阻塞队列、并发队列,底层都是队列这种数据结构,只不过是在之上附加了很多其他功能。阻塞队列就是入队、出队操作可以阻塞,并发队列就是队列操作多线程安全。