聊聊BlockingQueue阻塞队列

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 容量

无界还是有界

9.3 能否扩容

9.4 内存结构

9.5 性能

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Minor王智

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值