JUC并发工具---阻塞队列
阻塞队列也就是BlockingQueue,它是一个接口,继承了Queue接口,是队列的一种。
使用阻塞队列的话,就不会发生线程安全问题,降低我们的开发的难度和工作量(不用我们自己考虑线程安全,而是转移到队列上),实现了隔离,提升了安全性。
主要并发队列关系图
[外链图片转存中…(img-7Uko6uLa-1734860138402)]
阻塞队列 | 非阻塞队列 | Deque |
---|---|---|
ArrayBlockingQueue | ConcurrentLinkedQueue 用CAS保证了线程安全 | 双端队列 |
LinkedBlockingQueue | ||
SynchronousQueue | ||
DelayQueue | ||
PriorityBlockingQueue | ||
LinkedTransferQueue |
阻塞队列常用方法
add,remove,element
抛出异常
add():往队列里添加一个元素,如果队列满了,就会抛出异常来提示队列已满
remove():删除元素,如果我们删除的队列是空的,remove方法就会抛出异常
element():返回队列的头部节点但不删除,如果我们用这个方法去操作一个空队列,想获取队列的头结点,可是由于队列是空的,会抛出和前面remove方法一样的异常
offer,poll,peek
返回操作结果但不抛出异常
对应add(),remove(), element()方法
put,take
阻塞
put方法插入元素时:如果队列没有满,就和普通的插入一样是正常的插入,如果队列已满就无法继续插入,则阻塞,直到队列里有了空闲空间。如果后续队列有了空闲空间,队列就会解除阻塞状态,并把需要添加的数据添加到队列中
take 方法的功能:获取并移除队列的头结点,通常在队列里有数据的时候是可以正常移除的,可是一旦执行take方法的时候,队列里无数据,则阻塞,一旦队列里有数据就会立刻解除阻塞状态,并且取到数据
常见的阻塞队列
ArrayBlockingQueue | LinkedBlockingQueeu | SynchronousQueue | PriorityBlockingQueue | DelayQueue |
---|---|---|---|---|
有界,创建容量后不可扩容 | 无界,默认容量是整数的最大值Integer.MAX_VALUE | 容量为0,所做的就是直接传递 | 支持优先级的无界阻塞队列,后续可以扩容 | 无界 |
内部使用数组存储元素 | 内部使用链表实现 | 可以通过初始化构造器参数Comparator来指定排序规则。插入的对象必须是可以Comparable的 | 可以设定让队列中的任务延迟多久之后执行。 里面的元素必须实现Delayed接口 | |
利用ReentrantLock实现线程安全 | 次取数据都要先阻塞,直到有数据被放入,同理,每次放数据的时候也会阻塞,直到有消费者来取 |
阻塞和非阻塞队列的并发安全原理
[外链图片转存中…(img-Bu1eKWAi-1734860138402)]
[外链图片转存中…(img-kGcXpc58-1734860138402)]
如何选择
功能 | 比如是否需要阻塞队列帮我们排序,如优先级排序、延迟执行等 如果有这个需要,我们就必须选择类似于PriorityBlockingQueue之类的有排序能力的阻塞队列 |
---|---|
容量 | 或者说是否有存储的要求,还是只需要“直接传递” 在考虑这一点的时候,我们知道前面介绍的那几种阻塞队列,有的是容量固定的 如ArrayBlockingQueue;有的默认是容量无限的,如LinkedBlockingQueue;而有的里面没有任何容 量,如 SynchronousQueue;而对于DelayQueue 而言,它的容量固定就是Integer.MAX_VALUE 所以不同阻塞队列的容量是千差万别的,我们需要根据任务数量来推算出合适的容量,从而去选取合适 的 BlockingQueue |
能否扩容 | 因为有时我们并不能在初始的时候很好的准确估计队列的大小,因为业务可能有高峰期、低谷期如果一开 始就固定一个容量,可能无法应对所有的情况,也是不合适的,有可能需要动态扩容 如果我们需要动态扩容的话,那么就不能选择ArrayBlockingQueue,因为它的容量在创建时就确定了, 无法扩容 相反,PriorityBlockingQueue即使在指定了初始容量之后,后续如果有需要,也可以自动扩容所以我们 可以根据是否需要扩容来选取合适的队列 |
内存结构 | 在上一课时我们分析过ArrayBlockingQueue的源码,看到了它的内部结构是“数组”的形式 和它不同的是,LinkedBlockingQueue的内部是用链表实现的 所以这里就需要我们考虑到,ArrayBlockingQueue没有链表所需要的“节点”,空间利用率更高 所以如果我们对性能有要求可以从内存的结构角度去考虑这个问题 |
性能 | 比如LinkedBlockingQueue由于拥有两把锁,它的操作粒度更细,在并发程度高的时候,相对于只有 一把锁的ArrayBlockingQueue性能会更好 另外,SynchronousQueue性能往往优于其他实现,因为它只需要“直接传递”,而不需要存储的过 程。如果我们的场景需要直接传递的话,可以优先考虑SynchronousQueue |