初探java阻塞队列

java阻塞队列

Posted by KC on July 9, 2019

理解阻塞队列的应用场景

背景

提到阻塞队列,可能好多朋友并不陌生,java中面试频率较高的的线程池相关的技术就是由阻塞队列实现的。今天就让我们一起学习一下阻塞队列这个耳熟能闻却又容易被人忽略的技术。

阻塞队列接口是 Doug Lea 大神在java 1.5版本中引入的阻塞技术,从上图中可以看到其有多种具体实现,今天我们就研究一下相对较为常用的两个具体实现ArrayBlockingQueue和LinkedBlockingQueue

数据结构

望文生义,ArrayBlockingQueue的底层数据结构是通过数组来实现的,同时它也是线程安全的队列,通过加锁的方式来实现。LinkedBlockingQueue的底层数据结构是通过链表来实现的,他同样也通过加锁的方式实现线程安全。

通过不加锁的方式实现的队列都是无界的(无法控制队列的长度),而加锁的方式可以实现有界队列。在稳定性要求较高的系统中,为了防止生产者的生产速度过快,导致数据积压,进一步引发内存溢出,只能选择有界队列;

ArrayBlockingQueue和LinkedBlockingQueue都属于有界队列,通过上面两张图片可以看出,创建一个ArrayBlockingQueue必须指定队列长度,创建LinkedBlockingQueue时队列长度的参数是可选的,如果使用无参的构造函数的话,默认会使用 Integer.MAX_VALUE 来作为构造参数

同时,为了减少虚拟机gc对性能的影响,会尽量使用array格式的数据结构,因此两者中最符合条件的就是ArrayBlockingQueue了。

两种数据结构对性能的影响

看到这里可能有些小伙伴会有疑问了,为什么数组结构跟链表结构比起来对gc性能的影响最小呢?原因在于LinkedBlockingQueue在每次往队列中新增元素时都会创建一个node对象单向链接在链表的尾部,当该元素被消费后会把元素的载体node对象置为空交由gc回收,当队列中的元素过多时无疑会对系统gc产生较大的性能影响。而ArrayBlockingQueue由于内部维护了一个数组,在生产和消费的时候是直接将元素插入或移除的,不会产生或销毁任何对象实例,因此相对来说性能更为稳定。

链表

数组

加锁方式

除了数据结构的不同两者的加锁方式也有很大区别,ArrayBlockingQueue生产和消费时使用的是同一把锁,而LinkedBlockingQueue使用的锁是分离的,也就是说生产的时候使用putLock锁,消费的时候使用takeLock锁