队列
目录LinkedBlockingQueue 阻塞队列
- 新增操作
- add队列满的时候抛出异常
- offer队列满的时候返回false
- 查看并删除
- remove 队列空的时候抛出异常
- poll 队列空的时候返回null
- 只查看不删除操作
- element队列空的时候抛异常
- peek 队列空的时候返回null
- 类描述
- 基于链表的阻塞队列,其底层的数据结构是链表
- 链表维护先入先出队列,新元素被放在队尾,获取元素从队头拿。
- 链表大小在初始化的时候可以设置,默认是Integer的最大值
- 可以使用Collection和Iterator两个接口的所有操作,因为实现了两者的接口
- 内部构成
- 链表存储 + 锁 + 迭代器
- 链表的作用是保存当前节点,节点中的数据可以是任意 东西是一个泛型,比如说队列被应用到线程池时,节点就是线程。比如队列被应用到消息队列上,节点就是消息,节点的含义主要看队列被使用的场景
- 锁有take锁和put锁 是为了保证队列操作时的线程安全,设计两种锁,是为了take和put两种操作可以同时进行 互不影响
SynchronousQueue 交换队列
- 类描述
- 队列不存储数据,所以没有大小 无法迭代
- 插入操作的返回必须等待另一个线程完成对应数据的删除操作,反之亦然。
- 队列由两种数据结构祖册,分别是后入先出的堆栈和先入先出的队列。堆栈是非公平的,队列是公平的。
- 默认无参构造器是非公平的
- 栈实现
- 其阻塞的策略,并不是一上来就阻塞住,而是在自旋一定次数后,仍然没有其它线程来满足自己的要求时,才会真正的阻塞住。
- 队列实现
- 次 put 数据的时候,都 put 到队尾上,而每次拿数据时,并不是直接从队头拿数据,而是从队尾往前寻找第一个被阻塞的线程,这样就会按照顺序释放被阻塞的线程
DelayQueue 延时队列
- 类描述
- 队列中元素将在过期时被执行,越靠近队头,越早过期。
- 未过期的元素不能够被take
- 不允许空元素
- 实现要求
- delayQueue中的元素必须是Delayed的子类;Delayed是表达延迟能力的关键接口 其继承了Comparable接口 并定义了还剩多久过期的方法
- 内部排序用的是ProorityQueue队列 超时阻塞使用的事锁的等待能力
ArrayBlockingQueue 有界循环队列
- 类描述
- 有界的阻塞数组,容器一旦被创建,后续大小无法被修改
- 元素士有序的,按照先入先出进行排序,从队尾插入数据,从队头拿数据
- 队列满时,往队列中put 数据会被阻塞,从队列中拿数据也会被阻塞
- 数据结构
- takeIndex
- putIndex
- removeIndex
- private final ReentrankLock lock (可重入锁)
- private final Condition notEmpty (take的队列)
- private final Condition notFull (put的队列)
常见面试题
- 队列 如何实现阻塞的?
- 队列本身并没有实现阻塞的功能,而是利用 Condition 的等待唤醒机制,阻塞底层实现就是更改线程的状态为沉睡。
- 工作中经常使用队列put take方法有什么危害 如何避免?
- 两个方法都是无限阻塞 没有超时的意思,容易使得线程全部阻塞住,大流量时,导致机器无线程可用,所以在大流量的时候使用offer 和poll 方法来代替两者,只要设置好超时时间,在规定时间内未获取到值就会返回默认值,这样不会导致流量大时,所有线程都阻塞住了。
- 数据入队之后有没有办法让队列过一会在执行?
- 可以的,DelayQueue 提供了这种机制,可以设置一段时间之后再执行。
- 缺点:无法持久化,容易数据丢失,一般使用中间件的中间件会进行持久化
- DelayQueue对元素有要求么,把String可以放到队列中么?
- DelayQueue要求必须实现Delayed接口,Delayed 本身又实现了 Comparable 接口 , Delayed 接 口 的 作 用 是 定 义 还 剩 下 多 久 就 会 超 时 , 给 使 用 者 定 制 超 时 时 间 的 ,Comparable 接口主要用于对元素之间的超时时间进行排序的,两者结合,就可以让越快过期的元素能够排在前面。所以把 String 放到 DelayQueue 中是不行的,编译都无法通过,DelayQueue 类在定义的时候,是有泛型定义的,泛型类型必须是 Delayed 接口的子类才行。
- 如何看SynchronousQueue队列的大小?
- 无容量队列,其内部size方法返回的是0
- synchronousQueue 底层有两种数据结构,分别是队列和栈
- 队列维护了先入先出
- 栈先入后出
- 假设 SynchronousQueue 底层使用的是堆栈,线程 1 执行 take 操作阻塞住了,然后有线程 2 执行 put 操作,问此时线程 2 是如何把 put 的数据传递给take 的?
- 首先线程 1 被阻塞住,此时堆栈头就是线程 1 了,此时线程 2 执行 put 操作,会把 put 的数据赋值给堆栈头的 match 属性,并唤醒线程 1,线程 1 被唤醒后,拿到堆栈头中的 match 属性,就能够拿到 put 的数据了