当前位置: 代码迷 >> 综合 >> 【集合】Queue
  详细解决方案

【集合】Queue

热度:32   发布时间:2023-09-20 23:42:24.0

目录

一、Queue 概念

二、常见的Queue 解析

2.2 ArrayBlockingQueue

2.2.1 概念

2.2.2 特点

2.2.3 常用方法

2.2.4 结构和源码分析

2.3 LinkedBlockingQueue

2.3.1 概念

2.3.2 常用方法

2.3.3 关键点

2.4 ArrayBlockingQueue 和 LinkedBlockingQueue 对比


一、Queue 概念

Queue 是一种先进先出的结构,是线程安全的

 

二、常见的Queue 解析

2.2 ArrayBlockingQueue

2.2.1 概念

ArrayBlockingQueue 是一个数组结构的有界阻塞队列

 

2.2.2 特点

  • 线程安全
  • 初始化时必须指定大小

 

2.2.3 常用方法

方法名 描述 临界行为
队列尾部添加元素
add 添加一个元素 若队列已满,抛出 IIIegaISlabEepeplian 异常
offer 添加一个元素 若队列已满,返回 false。添加成功则返回 true
put 添加一个元素 若队列已满,则阻塞,等待有空闲位置
队列头部弹出元素
remove    移除并返回头部元素 若队列为空,则抛出 NoSuchElementException 异常
poll 移除并返回头部元素 若队列为空,返回 null
take 移除并返回头部元素 若队列为空,则阻塞
获取队列头部元素(不移除)
element   返回头部元素(不移除) 若队列为空,则抛出 NoSuchElementException 异常
peek  返回头部元素(不移除) 若队列为空,返回 null

 

2.2.4 结构和源码分析

查看 ArrayBlockingQueue 的源码,可以发现他的设计关键点:

  • 使用一个锁,写入和弹出都需要操作锁对象
  • 数组的容量有限,故内部节点会循环使用,使用一个写指针,和一个存指针,通过不断移动指针来实现存取

● 结构分析

当队列的数组没有满:

取指针始终指向第一个节点,即arr[0],取指针 takeIndex = 0。

而随着元素被推入队列,存指针始终是下一个要存入数据的数组索引,图中 putIndex = 5。

   【集合】Queue

当数组被填满时:

存指针重新移动到了数组第一位,putIndex = 0。

若此时有新元素要入队,根据调用的方法,可能被拒绝或阻塞。阻塞则是使用了 Condition 的方式等待

【集合】Queue

此时取走了三个节点:

当头部元素被弹出后,取指针也会不断后移,始终保存入对时间最长的元素的索引

如图中,数组中的 0 ,1,2 都被弹出了,取指针索引指向了3

    【集合】Queue

若原本有要入队的元素被阻塞,或是有新元素要入队:

由于存指针已经到了数组末尾,为了循环利用,则重新指向函数头。如插入了一个元素,则重新插入到 arr[0]的位置,且

修改存指针 putIndex = 1

          【集合】Queue

 

查看源码,列出关键属性

【集合】Queue

【集合】Queue

【集合】Queue

【集合】Queue

【集合】Queue

 

2.3 LinkedBlockingQueue

2.3.1 概念

LinkedBlockingQueue 是一种链表结构的阻塞队列,若在初始化时定义了容量,则为有界阻塞队列,反之则为无界阻塞队列。

 

2.3.2 常用方法

方法名 描述 临界行为
队列尾部添加元素
add 添加一个元素 若队列已满,抛出 IIIegaISlabEepeplian 异常
offer 添加一个元素 若队列已满,返回 false。添加成功则返回 true
put 添加一个元素 若队列已满,则阻塞,等待有空闲位置
队列头部弹出元素
remove    移除并返回头部元素 若队列为空,则抛出 NoSuchElementException 异常
remove(Object o) 移除指定元素 若队列为空,则抛出 NoSuchElementException 异常
poll 移除并返回头部元素 若队列为空,返回 null
take 移除并返回头部元素 若队列为空,则阻塞
获取队列头部元素(不移除)
element   返回头部元素(不移除) 若队列为空,则抛出 NoSuchElementException 异常
peek  返回头部元素(不移除) 若队列为空,返回 null

 

2.3.3 关键点

查看 LinkedBlockingQueue 的源码,发现设计关键点有:

  • 使用了两个锁,分别为入队锁和出队锁,这使得其并发效率高于 ArrayBlockingQueue
  • 若是不指定队列容量,则会变成无界队列(即Integet,MAX_VALUE),当出队速度小于入队速度时可能造成内存溢出(OOM)
  • 节点是在添加节点时动态分配的,后期需要GC回收
  • 在入队时使用入队锁,在出队时使用出队锁,但是在删除节点等操作时,同时会锁定出队锁和入队锁

 

2.4 ArrayBlockingQueue 和 LinkedBlockingQueue 对比

底层实现不同

ArrayBlockingQueue 内部是用数组实现的

LinkedBlockingQueue 内部是用链表实现的

● 锁机制不同

ArrayBlockingQueue 只有一个锁,出队和入队都要操作这个锁

而 LinkedBlockingQueue 有入队锁和出队锁,实现了锁分离,入队操作和出队操作互不影响,并发性更高

队列大小不同

ArrayBlockingQueue 是有界队列,初始化时必须指定容量,内部通过循环使用数组节点的方式

而 LinkedBlockingQueue 若是在初始化时指定了容量,则是有界队列,若不指定,则是无界队列(size = Integet.MAX_VALUE)

并发性能不同

LinkedBlockingQueue 实现了入队锁、出队锁分离,而ArrayBlockingQueue 入队出队都竞争同一把锁,故 LinkedBlockingQueue 并发效率更高

内存消耗不同

ArrayBlockingQueue 在初始化时已经开辟好了内存空间,使用过程中无需再动态申请存储空间

而 LinkedBlockingQueue 的节点的存储空间都是动态分配的,会增加 gc 回收的负担

● 吞吐量

由于 LinkedBlockingQueue 是无界队列,在大流量到来时能够存储更多的节点,且实现了锁分离,入队出队互不影响,故吞吐量高于ArrayBlockingQueue