type
status
date
slug
summary
tags
category
icon
password

先来看一下Queue的定义:
这些方法的区别:
操作 | 抛出异常 | 返回具体值 |
插入 | add(e) | offer(e) |
移除 | remove() | poll() |
检查(只查看) | element() | peek() |
BlockingQueue
BlockingQueue也是一个接口,继承自Queue。
如何理解这个“blocking”?
- *对于插入(入队)操作:**如果是无边界的队列,则直接插入;如果是有边界的队列,则需要等待队列中又空余容量的时候才能插入。
- *对于出队操作:**当队列不为空时,才会出队。
BlockingQueue还有如下特点:
- 线程安全
- 不允许null值
LinkedBlockingQueue
这是基于链表的BlockingQueue实现。
这里还有一个吞吐量的概念:队列每秒可以处理的消息数量,包括两个方面,一方面是发送的数量,一方面是接收的数量。
可以在构造方法传入一个容量,用来避免队列过度扩大,如果不指定容量,默认容量就位
Integer.MAX_VALUE
。通过
ReentranceLock
和Condition
来保证线程安全。先来看一下链表节点:
还有一些主要的fields:
由此知道LinkedBlockingQueue中包含了元素个数,容量边界,以及头尾节点等filed,还包含了用来保证线程安全性的takeLock,putLock,以及条件队列notEmpty和notFull。
插入
可以看到,队列不允许null值,并且通过ReentrantLock和Condition保证线程安全。
移除
操作和插入操作差不多。
ArrayBlockingQueue
基于数组实现的BlockingQueue。
属于典型的有界缓冲区,有固定的容量,一旦创建,容量无法修改。
在构造方法中必须传入一个容量参数,还可以传入一个参数用来指示队列的公平性访问问题;
可以看到,传入的fair参数实际上是用在ReentrantLock上的,用来表示用的是公平锁,还是非公平锁。
如果一个线程申请一把公平锁,那么当锁释放的时候,先申请的线程先得到锁,非常公平;
如果一个线程申请的是一把非公平锁,那么当锁释放的时候,后申请的线程可能会先得到锁,有随机性;
使用非公平锁的队列的吞吐量比使用公平锁的对垒的吞吐量要大,通常情况下都是优先使用非公平锁。
对于synchronized关键字来说,它也是一种非公平锁,而且没有任何方式可以将它变成公平锁。
插入
移除
SynchronousQueue
SynchronousQueue也是一种BlockingQueue,但是它比较特殊,它有如下特点:
- 并不存储任何元素,即容量为0,数据直接在配对的生产者/消费者之间传递,不会输入到缓冲队列中;
- 入队和出队线程必须一一匹配,要不然先到的线程会阻塞;
- 支持公平,非公平策略,默认为非公平策略。公平策略,基于内部的
TransferStack
结构实现,非公平策略基于内部的TransferQueue
结构实现;
- 基于一种无锁算法实现
从构造方法中可以看出其公平策略不同导致实现的不同:
这两个数据结构均是内部抽象类
Transfer
的实现:再来看一下它的size方法:
不会存储任何元素,容量为0.
插入
移除
PriorityQueue
基于数组(平衡二叉堆,优先堆)实现,元素是有序的,你需要实现Comparable,以方便元素进行比较,如果不实现,则按照自然顺序,如果队列不为空,则第一个元素为最小的元素。
这是一个无界的队列,默认容量大小为11,添加元素的时候会进行扩容。
总结一下特点吧:
- 基于数组实现,因而可以自由扩容,属于无界队列
- 有序(根据优先级,而优先级是通过Comparable实现进行比较),小的元素再前面
- 线程不安全
- 不支持null值
插入
可以看到其中有扩容操作,还有对堆的操作:
扩容方式
移除
依然是对数组的处理,关键点在处理堆的过程上。
PriorityBlockingQueue
PriorityBlockingQueue是一个阻塞队列,既然是阻塞队列,那么肯定有阻塞操作put和take。同时也是线程安全的。
它也是基于数组实现的,和PriorityQueue的存储结构无异,区别有以下几点:
- PriorityBlockingQueue是线程安全的(ReentrantLock保证);
- 支持阻塞操作put和take
DelayQueue
DelayQueue也是一个BlockingQueue,内部存储基于PriorityQueue,并通过ReentrantLock保证线程安全操作。
它有如下特点:
- 支持延迟获取元素(在创建的时候有一个过期时间,过期之后才可以获取元素)
- PriorityQueue是无界的,因此DelayQueue也是无界的
- 线程安全的
插入的元素类型必须实现Delay接口:
关键在于出队操作:
使用DelayQueue最常见的的场景有:
- 缓存系统的设计
使用DelayQueue保存带有效期的缓存元素,使用一个线程轮询DelayQueue,一旦获取到元素则表示缓存到期了;
- 定时任务调度
使用DelayQueue保存当天要执行的任务和时间,一旦获取到任务就开始执行,Timer中的TimingQueue就是基于DelayQueue实现的。
使用场景总结
队列一般用作基于生产-消费者模型的系统中,下面举一些比较常见的例子:
- 线程池的设计
JDK中的线程池设计使用到了各种BlockingQueue的实现,用来作为线程的容器。
比如固定线程数量和单个线程的ExecutorService,使用的是LinkedBlockingQueue;
缓存线程个数的ExecutorService,使用的是SynchronousQueue;
- 缓存系统的设计
比如前面提到的DelayQueue,元素可以带缓存时间。
- 定时调度的设计(如Timer)
比如前面提到的DelayQueue
- Android Framework中队列的使用
- Android 的AsyncLayoutInflater中使用的ArrayBlockingQueue;
- AnimationThread中使用了PriorityQueue;
- 作者:姜康
- 链接:https://jiangkang.tech/article/77e08648-3728-49c4-ad01-382b1ff5d31e
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。