多线程编程之阻塞队列概述

前言

JDK1.5中引入的接口java.util.concurrent.BlockingQueue定义了一种线程安全的队列-阻塞队列。BlockingQueue常用的实现类包括ArrayBlockingQueue,LinkedBlockingQueue和SynchronousQueues,下面来和大家一起学习下这三种阻塞队列。

阻塞队列描述

阻塞队列按照其存储空间的容量是否受限制来划分,可以分为有界队列和无界队列,有界队列的存储容量限制是由应用存储指定的,无界队列的最大存储容量为Integer.MAX_VALUE(2^{31}-1)个元素,往队列中存入一个元素的操作被称为put操作,从队列中取出一个元素的操作被称为take操作。put操作相当于生产线程将对象安全发布到消费者线程,生产者线程执行的put操作前所执行的任何内存操作,对于后续执行take操作的消费者线程而言都是可见的、有序的。

当消费者线程的处理能力低于生产者的处理能力时,产品的生产速率大于消费速率,这会导致队列中产品积压,即队列中的产品越来越多,由此导致队列中的这些对象所占用的内存空间以及其他资源越来越多,因此我们需要限制传输通道存储容量,此时,我们可以使用有界阻塞队列来作为传输通道。

使用有界队列作为传输通道的另一个好处时可以造成"反压"的效果,当消费者的处理能力跟不上生产者的处理能力时,队列中的产品会逐渐积压到队列满,此时生产者会被暂停掉,直到消费者消费了部分产品而使队列非满,这相当于生产者暂停其产品生产而给消费者一个跟上其步伐的机会,当然,这里的代价就是可能会增加上下文切换。

有界队列可以使用ArrayBlockingQueue或者LinkedBlockingQueue来实现,ArrayBlockingQueue内部使用一个数组作为其存储空间,而数组的存储空间是预先分配的,因为ArrayBlockingQueue的put操作和take操作本身不会增加垃圾回收的负担,ArrayBlockingQueue的缺点是其内部在实现put,take操作的时候使用的同一个锁,从而可能导致锁的高争用,进而导致较多的上下文切换。

LinkedBlockingQueue既能实现无界队列,也能实现有界队列。LinkedBlockingQueue的其中一个构造器允许我们创建队列的时候指定队列容量。LinkedBlockingQueue的优点是其在内部实现put,take操作的时候分别使用了两个显示锁,降低了锁争用的可能性。LinkedBlockingQueue的内部存储空间是一个链表,而链表节点所需的存储空间是动态分配的,put操作,take操作都会导致链表节点的动态创建和移除,因此LinkedBlockingQueue的缺点是它可能增加了垃圾回收的负担,另外LinkedBlockingQueue的put,take操作使用的是两个锁,因此LinkedBlockingQueue维护其队列长度(size)时无法使用一个普通的int变量而是使用一个原子变量。这个原子变量可能会被生产者线程和消费者线程争用。因此它可能导致额外的开销。

SynchronnousQueue可以被看作是一中特殊的有界队列,SynchronnousQueue内部并不维护用于存储队列元素的存储空间。设synchronnousQueue为一个任意的SynchronnousQueue实例,生产线程执行synchronnousQueue.put(E)时如果没有消费者线程执行synchronnousQueue.take(),那么该生产者线程会被暂停,直到消费者线程执行了synchronnousQueue.take();类似地,消费者线程执行synchronnousQueue.take()时如果没有生产者线程synchronnousQueue.put(E),那么消费者线程会被暂停,直到有生产者线程执行了synchronnousQueue.put(E)。因此,在使用SynchronnousQueue作为传输通道的生产者-消费者模式中,生产者生产一个产品之后,会等待消费者线程来取走这个产品才继续生产下一个产品,而不像使用ArrayBlockingQueue或者LinkedBlockingQueue作为传递通道的情况下生产者线程将生产好的产品存入队列就继续生产下一个产品。从这个点来看,ArrayBlockingQueue和LinkedBlockingQueue更像是一个信箱;邮递员只需要将普通邮件投递到指定信箱即可。而不必关心收件人何时会取走邮件;而SynchronnousQueue所实现的通道更像是邮递员投送挂号信与收件员接触的情形-邮递员必须在收件人签收之后才能离开。因此SynchronnousQueue适合于消费者处理能里和生产者处理能力相差不大的情况下使用。否则由于生产者线程执行put操作时没有消费者线程执行take操作,或者消费者线程执行take操作的时候没有生产者线程执行put的操纵概率比较大,从而导致较多的等待(这意味着上下文的切换)。

队列可以被看作时生产者和消费者之间的共享资源,因此资源调度的公平性在队列上也有所体现。占用队列的线程可以对队列进行put或take操作,那么对队列的调度就是决定哪个线程可以进行put或者take操作的过程。ArrayBlockingQueue和LinkedBlockingQueue都支持非公平调度也支持公平调度,而LinkedBlockingQueue仅支持非公平调度。

如果生产者线程和消费者线程之前的并发程度较大,那么这些线程对传输通道内部所使用的锁争用可能性也会随之增大。这时,有界队列的实现适合采用LinkedBlockingQueue,否则我们可以考虑采用ArrayBlockingQueue。

总结

LinkedBlockingQueue适合在生产者线程和消费者线程之间的并发程度较大的情况下使用,ArrayBlockingQueue适合生产者线程和消费者线程之间的并发程度较低的情况下使用,SynchronnousQueue适合在消费者处理能力和生产者处理能力相差不大的情况下使用。

文章来源

摘自-《java多线程编程实战指南》

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

爱琴孩

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

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

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

打赏作者

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

抵扣说明:

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

余额充值