笔记目录
1.阻塞式队列 BlockingQueue
阻塞式队列顾名思义在队列FIFO的结构模式下增加了阻塞的功能,也就是说给队列添加元素和弹出元素的操作会有阻塞的行为。
1.1 队列 Queue(接口)
Queue接口继承自Collection,属于Java集合的一员。它提供了一系列的对队列这种FIFO结构的抽象方法。
- boolean add(E e) 添加元素,成功就返回true。如果队列满了就会抛异常。
- boolean offer(E e) 添加元素,成功就返回true。如果队列满了就返回false。
- E remove() 返回并删除队首的元素,队列如果为空则抛出异常。
- E poll() 返回并删除队首的元素,队列如果为空则返回null。
- E element() 返回队首元素,不移除,队列如果为空则抛出异常。
- E peek() 返回队首元素,不移除,队列如果为空则返回null。
1.2 阻塞队列 BlockingQueue(接口)
BlockingQueue继承了Queue。在队列的基础上拓展了阻塞地获取和移除的手段。
1.2.1 阻塞式入队
offer(E e)
:如果队列没满,返回true。如果队列满了,返回false,不阻塞。
offer(E e, long timeout, TimeUnit unit)
:如果队列已满,则进行阻塞,超过 阻塞时间则返回false。
put(E e)
:队列没满,返回true。如果队列满了,则阻塞,直到有空为止。
1.2.2 阻塞式出队
poll( )
:如果队首有数据,则出队,如果没有则返回null,不阻塞。
poll(long timeout, TimeUnit unit)
:如果队列有数据则返回,没有则阻塞,直到 超时。
take( )
:队列里有数据则弹出队首并删除,如果无数据,则阻塞直到有数据为止。
2.常见的阻塞式队列实现类
阻塞式队列实现 | 特点 |
---|---|
ArrayBlockingQueue | 基于数组实现、有界 |
LinkedBlockingQueue | 基于链表实现、理论无界(实际有界) |
PriorityBlockingQueue | 优先级队列、理论无界 |
DeleyQueue | 基于优先级队列实现、延时功能 |
SynchronousQueue | 没有元素节点的阻塞队列 |
LinkedTransferQueue | 基于链表实现、无界队列 |
LinkedBlockingDeque | 基于链表实现、双端阻塞队列 |
3.ArrayBlockingQueue
3.1 ArrayBlockingQueue特点
- 底层使用数组实现,所以构造需要传入队列的初始长度。
- 使用ReentrantLock和条件队列来保证线程的安全和阻塞的功能。
- 出队和入队的功能使用同一把锁,性能较低。
- 适合生产速度和消费速度比较匹配的情况。
- 初始化数组长度就是传入的容量,并且容量固定,不会扩缩容。
- notEmpty和notFull基于两个不同的条件队列,但是都是同一把锁。
3.2 数组的双指针设计
每当生产者put的时候,putIndex就是指向空的节点,put完成后,putInde就会挪到下一个位置,如果当前指针已经到了数组尾巴,则指针指向index = 0的位置。设计成双指针的目的是为了达成一个环形数组的样子,避免了数组节点的位移挪动的时间复杂度开销,双指针的设计时间复杂度是O1。
4.LinkedBlockingQueue
4.1 LinkedBlockingQueue特点
- 底层基于链表实现,支持无参构造和带参构造,参数就是队列的容量。
- 无参构造的容量是Integer.MAX_VALUE。理论是无界的,实际是有界的。
- 由于是链表,几乎是无界,所以滥用它容易造成OOM。
- 链表是单向的。
- 出队和入队分别使用不同的锁,控制粒度更细。出队、入队互不干扰。
- 两把锁各自使用各自的一个条件队列。
5.ArrayBlockingQueue和LinkedBlockingQueue比较
- 队列的容量有区别,前者需要初始化时就要指定大小,后者支持指定和不指定初始化大小;如果不指定初始化大小,容量就是int的最大值。
- 前者底层是数组,后者是链表。所以前者对内存的规整度有要求,后者则更加容易造成OOM。
- 前者的入队出队动作是同一把锁,用不同条件队列来阻塞。后者的出队入队分别用不同的锁,分别用不同的条件队列来阻塞。所以后者的并发性能更高,所以线程池的默认队列就是后者。
6.SynchronousQueue 同步阻塞队列
同步阻塞队列没有数据缓冲,生产者的入队操作必须等待消费者的出队操作。适合数据交互的场景。
SynchronousQueue s = new SynchronousQueue<>();
6.1 SynchronousQueue特点
- 容量为0,不会缓冲数据。
- 入队要阻塞等待出队线程的到来,出队消费阻塞要等到入队线程的到来。
- 适合信息、事件等传递性的场景。
- Executors.newCachedThreadPool( )就用到了同步阻塞队列。
- CAS+自旋模式进行出入队逻辑,自旋一定次数后才会阻塞调用LockSupport.park()。
- 公平模式:FIFO,非公平模式(默认):栈结构,FILO。
7.DelayQueue 延时队列
延迟队列类似MQ的延时消息,元素不会立即让消费者消费出队而是根据元素的延迟时间排序,越短的排在前面,当时间到了以后就能够被消费者出队消费。DelayQueue d = new DelayQueue<>();
class A implements Delayed,重写#getDelay()和#compareTo(Delayed d);
7.1 DelayQueue特点
- 元素的类必须实现Delayed接口,重写getDelay和compareTo方法。
- 基于优先级队列(堆)、无界。
- 阻塞基于ReentrantLock一把锁,出入队基于条件队列。
- 检查堆顶元素的过期时间,小于等于0则出队,否则阻塞。
- 适合延迟消费的场景,例如订单超时、缓存等。
8.PriorityBlockingQueue 优先级阻塞队列
优先级阻塞队列是一个无界的基于数组的优先级阻塞队列,数组初始默认长度11,可以无限扩充。每次出队都返回优先级最高的or最低的元素,默认情况下采用自然排序升序来出队,也可以再构造中指定comparator排序规则,优先级阻塞队列不能保证同优先级元素的顺序。
PriorityBlockingQueue p = new PriorityBlockingQueue<>(20);
PriorityBlockingQueue p = new PriorityBlockingQueue<>(20, new Comparator(){
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;}
})
8.1 优先级阻塞队列的特点
- 优先级、排序、无界。
- 基于数组的二叉堆实现,默认容量11,可以指定,自动扩容,最大容量int.max - 8;
- 出入队基于一把锁,ReentrantLock。
- 出队基于条件队列,入队用lock,但是不会因为容量问题阻塞。
9.阻塞式队列的选择(选择策略)
9.1 功能
是否支持数据的缓冲?是否需要延迟、排序等功能。
9.2 容量
无界还是有界