极客算法05 | 队列

版权声明:本文为博主原创文章,转载请附上博文链接! https://blog.csdn.net/weixin_40862011/article/details/88364731

队列也是一种“操作受限”的线性表,只支持两种基本操作:入队和出队,满足先进先出的特性。

队列的应用非常广泛,特别是一些具有某些额外特性的队列,比如循环队列、阻塞队列、并发队列。它们在很多偏底层的系统、框架、中间件的开发中,起着关键性的作用。比如高性能队列 Disruptor、Linux 环形缓存,都用到了循环并发队列;Java concurrent 并发包利用 ArrayBlockingQueue 来实现公平锁等。

顺序队列和链式队列

用数组实现的队列叫顺序队列,用链表实现的队列叫链式队列。

顺序队列在没有空闲空间时,需要触发一次数据的搬移操作。

循环队列

将顺序队列首尾连接起来就是循环队列。要注意的点就是,队列判满的条件是(tail + 1) % n = head。

阻塞队列和并发队列

阻塞队列就是在队列的基础上增加了阻塞操作。简单来说,就是在队列为空的时候,出队操作会被阻塞,当队列满的时候,入队操作会被阻塞。其实,上面的定义,就是一个“生产者-消费者”模型。

这种模型,可以有效协调生产和消费的速度。当“生产者”生产速度过快,“消费者”来不及消费时,存储队列很快就会满了。这个时候,生产者就阻塞等待,直到“消费者”消费了数据,“生产者”才会被唤醒继续生产。

而且不仅如此,基于阻塞队列,我们还可以协调“生产者”和“消费者”的个数,来提高数据处理的效率,比如,我们可以多配置几个“消费者”,来对应一个“生产者”。

在多线程情况下,会有多个线程同时操作队列,这个时候就会存在线程安全问题,那如何实现一个线程安全的队列呢?

并发队列:线程安全的队列我们叫做并发队列。最简单的实现方式就是直接在 enqueue()、dequeue() 方法上加锁,但是锁粒度大并发度会比较低。实际上,基于数组的循环队列,利用CAS 原子操作,可以实现非常高效的并发队列。这也是循环队列比链式队列应用更加广泛的原因。

队列在线程池中的应用

当线程池没有空闲线程时,新的任务请求线程资源时,线程池该如何处理?各种处理策略又是如何实现的呢?

我们一般有两种处理策略一种是非阻塞队列,一种是阻塞队列。

非阻塞队列是直接拒绝任务请求。

阻塞队列又有两种实现方式:

一种基于链表的实现方式,可以实现一个支持无限排队的无界队列,但是可能会导致过多的请求排队等待,请求处理的响应时间过长。所以,针对响应时间比较敏感的系统,基于链表实现的无限排队的线程池是不合适的。

另一种就是基于数组的有限队列,队列的大小有限,所以线程池中排队的请求超过队列大小时,接下来的请求就会被拒绝。这种方式对响应时间敏感的系统来说,就相对更加合理。不过给数组队列设置一个合理的队列大小,是非常有讲究的。队列太大导致等待的请求太多,队列太小会导致无法充分利用系统资源、发挥最大性能。

实际上,对于大部分资源有限的场景,当没有空闲资源时,基本上都可以通过“队列”这种数据结构来实现请求排队。

关于如何实现无锁并发队列

可以使用 cas + 数组的方式实现。

队列的其他应用

分布式消息队列,如 kafka 也是一种队列。

猜你喜欢

转载自blog.csdn.net/weixin_40862011/article/details/88364731