ArrayBlockingQueue
概述
ArrayBlockingQueue
,顾名思义:基于数组的阻塞队列。数组是要指定长度的,所以使用 ArrayBlockingQueue
时必须指定长度,也就是它是一个有界阻塞队列。(有界是指他的容量大小是固定的,不能扩充容量,在初始化时就必须确定队列大小。)
它实现了 BlockingQueue
接口,有着队列、集合以及阻塞队列的所有方法。它内部使用 ReentrantLock
来保证线程安全。ArrayBlockingQueue
支持对生产者线程和消费者线程进行公平的调度,默认情况下是不保证公平性的。公平性通常会降低吞吐量,但是减少了可变性和避免了线程饥饿问题。
常用操作
取数据
take()
:首选,当队列为空时阻塞。poll()
:弹出队顶元素,队列为空时,返回空。peek()
:和poll
类似,返回队顶元素,但顶元素不弹出。队列为空时返回null
。remove(Object o)
:移除某个元素,队列为空时抛出异常。成功移除返回true
。
添加数据
put()
:首选,队满时阻塞。add()
:插入元素到队尾,插入成功返回true
,没有可用空间抛出异常IllegalStateException
。offer()
:队满时返回false
。(插入元素到队尾,插入成功返回true
,否则返回false
。)
总结
ArrayBlockingQueue
是一个阻塞队列,内部由 ReentrantLock
来实现线程安全,由 Condition
的 await
和 signal
来实现等待唤醒的功能。它的数据结构是数组,准确的说是一个循环数组(可以类比一个圆环),所有的下标在到达最大长度时自动从 0 继续开始。
案例:
1 | /** |
LinkedBlockingQueue
概述
LinkedBlockingQueue
内部由单链表实现,只能从 head
取元素,从 tail
添加元素。添加元素和获取元素都有独立的锁,也就是说 LinkedBlockingQueue
是读写分离的,读写操作可以并行执行。LinkedBlockingQueue
采用可重入锁(ReentrantLock
)来保证在并发情况下的线程安全。
构造器
LinkedBlockingQueue
一共有三个构造器,分别是无参构造器、可以指定容量的构造器、可以穿入一个容器的构造器。如果在创建实例的时候调用的是无参构造器,LinkedBlockingQueue
的默认容量是 Integer.MAX_VALUE
,这样做很可能会导致队列还没有满,但是内存却已经满了的情况(内存溢出)。
1 | //设置容量为 Integer.MAX |
常用操作
取数据
take()
:首选,当队列为空时阻塞。poll()
:弹出队顶元素,队列为空时,返回空。peek()
:和poll
类似,返回队顶元素,但顶元素不弹出。队列为空时返回null
。remove(Object o)
:移除某个元素,队列为空时抛出异常。成功移除返回true
。
添加数据
put()
:首选,队满时阻塞。add()
:插入元素到队尾,插入成功返回true
,没有可用空间抛出异常IllegalStateException
。offer()
:队满时返回false
。(插入元素到队尾,插入成功返回true
,否则返回false
。)
判断队列是否为空
size()
方法会遍历整个队列,时间复杂度为 O(n),所以最好选用isEmtpy()
。
总结
LinkedBlockingQueue
是一个阻塞队列,内部由两个 ReentrantLock
来实现出入队列的线程安全,由各自的 Condition
对象的 await
和 signal
来实现等待和唤醒功能。它和 ArrayBlockingQueue
的不同点在于:
-
队列大小有所不同,
ArrayBlockingQueue
是有界的初始化必须指定大小,而LinkedBlockingQueue
可以是有界的也可以是无界的(Integer.MAX_VALUE
),对于后者而言,当添加速度大于移除速度时,在无界的情况下,可能会造成内存溢出等问题。 -
数据存储容器不同,
ArrayBlockingQueue
采用的是数组作为数据存储容器,而LinkedBlockingQueue
采用的则是以Node
节点作为连接对象的链表。 -
由于
ArrayBlockingQueue
采用的是数组的存储容器,因此在插入或删除元素时不会产生或销毁任何额外的对象实例,而LinkedBlockingQueue
则会生成一个额外的Node
对象。这可能在长时间内需要高效并发地处理大批量数据的时,对于GC
可能存在较大影响。 -
两者的实现队列添加或移除的锁不一样,
ArrayBlockingQueue
实现的队列中的锁是没有分离的,即添加操作和移除操作采用的同一个ReenterLock
锁,而LinkedBlockingQueue
实现的队列中的锁是分离的,其添加采用的是putLock
,移除采用的则是takeLock
,这样能大大提高队列的吞吐量,也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。
案例:
1 | /** |
If you like this blog or find it useful for you, you are welcome to comment on it. You are also welcome to share this blog, so that more people can participate in it. If the images used in the blog infringe your copyright, please contact the author to delete them. Thank you !