java线程获取队列消费_队列:生产消费模式及线程池的运用

本文探讨了在Java线程池中,当资源耗尽时如何处理新请求,引入了队列作为存储等待处理任务的数据结构。讲解了队列的基本概念,如先进先出特性,并通过数组和链表实现队列,还介绍了循环队列以避免数据迁移带来的性能影响。此外,提到了队列在系统、框架和中间件开发中的关键作用,如Disruptor和ArrayBlockingQueue。
摘要由CSDN通过智能技术生成

队列:生产消费模式及线程池的运用

关注公众号 MageByte,设置星标获取最新干货。 “加群” 进入技术交流群获更多技术成长。

向固定大小的线程池投放请求任务时,若果线程池中没有空闲资源了,这时候还有新的请求进来,线程池如何处理这个请求?拒绝请求还是排队?使用怎样的处理机制

一般两种策略:

直接拒绝任务请求;

将请求排队,等有空闲线程的时候取出排队的请求继续处理。

那如何存储排队的请求呢?这就是今天要讲的话题。

其底层的数据结构就是今天我们要讲的内容,「队列」Queue。

完整代码详见 GitHub:https://github.com/UniqueDong/algorithms.git

什么是队列

用一个生活例子,可以想象成超市排队结账,先来的先结账,后面的人只能站在末尾,不允许插队。「先进先出,这就是所谓的「队列」」

队列是一种线性数据结构,队列的出口端叫「队头」,队列的入口端叫「队尾」。

与栈类似队列的数据结构可以使用数组实现也可以使用链表实现。关于栈的内容同学们可以翻阅历史文章学习「栈:实现浏览器前进后退」,队列最基本的操作也是两个:「入队 (enqueue)」 ,将新元素放到队尾;「出队 (dequeue)」,从队头移除元素,出队元素的下一个元素变成新的队头。

作为基础的数据结构,队列的应用也很广泛,尤其是一些特定场景下的队列。比如循环队列、阻塞队列、并发队列。它们在很多偏底层系统、框架、中间件的开发中,起着关键性的作用。比如高性能队列 Disruptor、Linux 环形缓存,都用到了循环并发队列;Java concurrent 并发包利用 ArrayBlockingQueue 来实现公平锁等。

a4bb39d90f5949410f742601abb509bc.png队列与栈

队列也是一种操作受限的线性表数据结构。

顺序队列与链式队列

队列是跟栈一样,是一种抽象的数据结构。 「具有先进先出的特性,在队头删除数据,在队尾插入数据。」

可以使用数组实现,也可以使用链表实现。使用数组实现的叫 「顺序队列」,用链表实现的 叫 「链式队列」。

顺序队列

一起先来看数组实现的队列:

出队操作就是把元素移除队列,只允许在队头移除,出队的下一个元素成为新的队头。

入队操作就是把新元素放入队列,只允许在队尾插入,新元素的的下一个位置成为队尾。

「随着不停地进行入队、出队操作,head 和 tail 都会持续往后移动。当 tail 移动到最右边,即使数组中还有空闲空间,也无法继续往队列中添加数据了。这个问题该如何解决呢?」

当出现这种情况的时候我们就需要做数据迁移。如图所示:当 abcd 入队后,对应的指针位置。

2171b98fe1f5d128c82353aa29fc0141.png

现在我们执行出队操作

74354461672691418552fe16423e7d11.png

当我们调用两次出队操作之后,队列中 head 指针指向下标为 2 的位置,tail 指针仍然指向下标为 4 的位置。

迁移操作其实就是把整段数据移动到数组 0 开始的位置。

98807fb8f94dc0b718b25fc52d628b9b.png

具体代码如下

/**

* 数组实现队列

*/

public class ArrayQueue extends AbstractQueue {

/**

* The queued items

*/

final E[] items;

/**

* 队头指针

*/

private int front;

/**

* 队尾指针

*/

private int rear;

/**

* Creates an ArrayQueue with the given capacity

*

* @param capacity the capacity of this queue

*/

public ArrayQueue(Class type, int capacity) {

if (capacity <= 0) {

throw new IllegalArgumentException();

}

this.items = (E[]) Array.newInstance(type, capacity);

}

public int capacity() {

return items.length;

}

@Override

public E dequeue() {

if (front == rear) {

throw new IllegalStateException("Queue empty");

}

return items[front++];

}

@Override

public boolean enqueue(E e) {

if (isFull()) {

throw new IllegalStateException("Queue empty");

}

// 队尾没有空间了,需要执行数据迁移

if (rear == capacity()) {

// 数据迁移

if (rear - front >= 0) {

System.arraycopy(items, front, items, 0, rear - front);

}

// 调整 front 与 rear

rear -= front;

front = 0;

}

items[rear++] = e;

return true;

}

@Override

public boolean isFull() {

return rear == capacity() && front == 0;

}

@Override

public boolean isEmpty() {

return front == rear;

}

}

复制代码

链式队列

我们可以通过之前学习过的链表来实现队列,具体详见单向链表篇 。其实主要就是利用了 「出队就是链表头删除数据,入队就是尾节点添加数据」

public class LinkedQueue extends AbstractQueue implements Queue {

private final SingleLinkedList linkedList;

public LinkedQueue() {

this.linkedList = new SingleLinkedList<>();

}

@Override

public E dequeue() {

if (linkedList.isEmpty()) {

throw new IllegalStateException("Queue empty");

}

return linkedList.remove();

}

@Override

public boolean enqueue(E e) {

return linkedList.add(e);

}

@Override

public boolean isFull() {

return false;

}

@Override

public boolean isEmpty() {

return linkedList.isEmpty();

}

}

复制代码

循环队列

刚刚的例子,当 rear == capacity 的时候,会出现数据迁移操作,这样性能受到影响,那如何避免呢?

原本数组是有头有尾的,是一条直线。现在我们把首尾相连,扳成了一个环。

ca77d14d66a24b931da51d877c2d8852.png环形队列

我们可以看到,图中这个队列的大小为 8,当前 head=4,tail=7。当有一个新的元素 a 入队时,我们放入下标为 7 的位置。但这个时候,我们并不把 tail 更新为 8,而是将其在环中后移一位,到下标为 0 的位置。当再有一个元素 b 入队时,我们将 b 放入下标为 0 的位置,然后 tail 加 1 更新为 1。所以,在 a,b 依次入队之后,循环队列中的元素就变成了下面的样子:

ab0f31b6e5c4e39765018ef29b6ddf7c.png

「队列为空的判断依然是 front == rear,队列满的条件则是 (rear + 1) % capacity = front」

你有没有发现,当队列满时,图中的 tail 指向的位置实际上是没有存储数据的。所以,循环队列会浪费一个数组的存储空间。

/**

* 数组实现环形队列

*

* @param

*/

public class ArrayCircleQueue extends AbstractQueue {

/**

* The queued items

*/

final E[] items;

/**

* 队头指针

*/

private int front;

/**

* 队尾指针

*/

private int rear;

public int capacity() {

return items.length;

}

/**

* Creates an ArrayQueue with the given capacity

*

* @param capacity the capacity of this queue

*/

public ArrayCircleQueue(Class type, int capacity) {

if (capacity <= 0) {

throw new IllegalArgumentException();

}

this.items = (E[]) Array.newInstance(type, capacity);

}

@Override

public E dequeue() {

if (front == rear) {

throw new IllegalStateException("Queue empty");

}

E item = items[front];

front = (front + 1) % items.length;

return item;

}

@Override

public boolean enqueue(E e) {

checkNotNull(e);

int newRear = (rear + 1) % items.length;

if (newRear == front) {

throw new IllegalStateException("Queue full");

}

items[rear] = e;

this.rear = newRear;

return true;

}

@Override

public boolean isFull() {

return (rear + 1) % items.length == front;

}

@Override

public boolean isEmpty() {

return rear == front;

}

}

复制代码

推荐阅读

1.跨越数据结构与算法

2.时间复杂度与空间复杂度

3.最好、最坏、平均、均摊时间复杂度

4.线性表之数组

5.链表导论-心法篇

6.单向链表正确实现方式

7.双向链表正确实现

8.栈实现浏览器的前进后退

原创不易,觉得有用希望随手「在看」「收藏」「转发」三连。

本文使用 mdnice 排版

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值