Java BlockingQueue接口java.util.concurrent.BlockingQueue表示一个可以存取元素,并且线程安全的队列。换句话说,当多线程同时从 JavaBlockingQueue中插入元素、获取元素的时候,不会导致任何并发问题(元素被插入多次、处理多次等问题)。
从java BlockingQueue可以引申出一个概念:阻塞队列,是指队列本身可以阻塞线程向队列里面插入元素,或者阻塞线程从队列里面获取元素。比如:当一个线程尝试去从一个空队列里面获取元素的时候,这个线程将被阻塞直到队列内元素数量不再为空。当然,线程是否会被阻塞取决于你调用什么方法从BlockingQueue获取元素,有的方法会阻塞线程,有的方法会抛出异常等等,下文我们会详细介绍。
一、BlockingQueue 接口实现类
本文不会去介绍如何自己实现BlockingQueue接口,JUC已经为我们做好了相关的一些接口实现类。BlockingQueue是一个java接口,当我们需要使用阻塞队列的时候,可以使用它的实现类。java.util.concurrent(简称JUC)包里面有如下的一些实现类实现了BlockingQueue接口。
- ArrayBlockingQueue
- DelayQueue
- LinkedBlockingQueue
- LinkedBlockingDeque
- LinkedTransferQueue
- PriorityBlockingQueue
- SynchronousQueue
在本文以及后续的文章中,会依次为大家介绍这些实现类的作用及使用场景,期待您的关注。
二、BlockingQueue 应用场景介绍
BlockingQueue通常被应用在一个线程生产对象放入队列,与此同时另一个线程消费队列内的对象的场景下。下面的这张图说明了使用场景:
生产者线程不断的生产新的对象,并将他们插入到BlockingQueue,直到队列中object的数量达到队列存储容量的上限。也就是说当队列中对象达到容量上限的时候,生产者线程将被阻塞,不能再向队列中插入新的对象。生产者线程将保持阻塞等待状态,直到消费者线程从队列中拿走Object,让队列有空余位置放入新的对象。
消费者线程不断的从BlockingQueue取出对象并将其进行处理。如果消费者线程尝试从一个空队列中获取一个对象,消费者线程将被阻塞处于等待状态,直到生产者向队列中放入一个新的对象。
所以BlockingQueue经常被用于生产消费的缓冲队列,如果你不想用分布式的或者中间件消息队列(redis、kafka)等(因为对于一个小功能会增加比较大的独立中间件运维成本),BlockingQueue可以能是一个备选的选项。
2.1.BlockingQueue 方法介绍
JavaBlockingQueue 提供了四组不同的方法用于向队列中插入、移除、检查队列中包含某一元素对象。每一组方法在被调用之后的响应行为上有所不同,如下:
抛出异常 | 返回特定值 | 阻塞后一直等待 | 阻塞后等待超时 | |
---|---|---|---|---|
插入对象 | add(o) | offer(o) | put(o) | offer(o, timeout, timeunit) |
移除对象 | remove(o) | poll() | take() | poll(timeout, timeunit) |
检查对象存在 | element() | peek() |
上面的方法的四种行为分别的含义是
- 抛出异常: 如果调用方法后不能立即响应结果(空队列或满队列),则抛出异常。
- 返回特定值: 如果调用方法后不能立即响应结果(空队列或满队列),则返回特定的值(通常是true/false),true表示方法执行成功,否则表示方法执行失败。
- 阻塞后一直等待: 如果调用方法后不能立即响应结果(空队列或满队列),该方法将被阻塞一直处于等待状态。
- 阻塞后等待超时: 如果调用方法后不能立即响应结果(空队列或满队列),该方法将在一定时间范围内被阻塞等待,也就是在超时时间范围内阻塞。当超出超时时间之后,方法线程将不再阻塞,而是返回一个特定的值(通常是true/false),true表示方法执行成功,否则表示方法执行失败。
另外,BlockingQueue队列不允许向其内部插入null,如果你向队列中插入null,将会引发NullPointerException异常。一般的队列都是从队首放入对象,从队尾获取对象,BlockingQueue不仅支持从队首队尾操作数据对象,还支持从队列中其他任何位置操作数据。比如:你已经向队列中放入一个对象并等待处理,但是出于某些特殊原因希望将这个对象从队列中删除掉。你可以调用remove(o)方法来删除队列中的一个特定的o对象。当然我们的程序不能经常性的这样做,因为队列这种数据结构经常从中间位置操作数据的效率是极低的,所以除非必要不建议这样做。
add(o)
BlockingQueueadd() 方法可以将o对象以参数的形式插入到队列里面,如果队列里面有剩余空间,将被立即插入;如果队列里面没有剩余空间,add()方法将跑出 IllegalStateException.
offer(o)
BlockingQueueoffer() 方法可以将o对象以参数的形式插入到队列里面,如果队列里面有剩余空间,将被立即插入;如果队列里面没有剩余空间,offer()方法将返回特定的值false.
offer(o, long millis, TimeUnit timeUnit)
BlockingQueueoffer() 方法有另外一个版本的实现,存在超时时间的设置参数。这个版本的offer()方法将o对象以参数的形式插入到队列里面,如果队列里面有剩余空间,将被立即插入;如果队列里面没有剩余空间,调用offer方法的线程在超时时间内将被阻塞处于等到状态,当阻塞时间大于超时时间之后,队列内如果仍然没有剩余空间放入新对象,offer()方法将返回false.
put(o)
BlockingQueueput() 方法可以将o对象以参数的形式插入到队列里面,如果队列里面有剩余空间,将被立即插入;如果队列里面没有剩余空间,调用put的方法的线程将被阻塞,直到BlockingQueue里面腾出新的空间可以放入对象为止。
take()
BlockingQueuetake() 方法取出并移除队列中的第一个元素(对象),如果BlockingQueue队列中不包含任何的元素,调用take()方法的线程将被阻塞,直到有新的元素对象插入到队列中为止。
poll()
BlockingQueuepoll() 方法取出并移除队列中的第一个元素(对象),如果BlockingQueue队列中不包含任何的元素,poll()方法将返回null.
poll(long timeMillis, TimeUnit timeUnit)
BlockingQueuepoll(long timeMillis, TimeUnit timeUnit)方法同样存在一个超时时间限制的版本,正常情况下该方法取出并移除队列中的第一个元素(对象)。如果BlockingQueue队列中不包含任何的元素,在超时时间范围内,如果仍然没有新的对象放入队列,这个版本的poll()方法将被阻塞处于等待状态;当阻塞时间大于超时时间之后,poll(long timeMillis, TimeUnit timeUnit)返回null
remove(Object o)
BlockingQueueremove(Object o) 方法可以从队列中删除一个以参数形式给定的元素对象,remove()方法使用o.equals(element)将传入参数o与队列中的对象进行一一比对,从而判定要删除的对象是否在队列中存在,如果存在就从队列中删除并返回true,否则返回false。
需要注意的是:如果队列中有多个与传入参数equals相等的对象,只删除其中一个,不会将队列中所有匹配的对象都删除。
peek()
BlockingQueuepeek() 方法将取出队列中的第一个元素对象,但是并不会将其从队列中删除。如果队列中目前没有任何的元素,也就是空队列,peek()方法将返回null.
element()
BlockingQueueelement()方法将取出队列中的第一个元素对象,但是并不会将其从队列中删除。如果队列中目前没有任何的元素,也就是空队列,element()方法将抛出 NoSuchElementException.
contains(Object o)
BlockingQueuecontains(Object o) 方法用来判断当前队列中是否存在某个对象,该对象与传入参数o相等(Objects.equals(o, element)被用来判定对象的相等性)。遍历队列中的所有元素,一旦在队列中发现匹配的元素对象,该方法将返回true;如果没有任何的元素匹配相等,该方法返回false。
drainTo(Collection dest)
drainTo(Collection dest)方法一次性的将队列中的所有元素取出到集合类Collection dest对象中保存。
drainTo(Collection dest, int maxElements)
drainTo(Collection dest)方法一次性的从队列中取出maxElements个元素到集合类Collection dest对象中保存。
size()
BlockingQueuesize() 方法返回队列中目前共有多少个元素
remainingCapacity()
BlockingQueueremainingCapacity() 方法将返回队列目前还剩多少个可用空间用于放入新的对象。剩余空间容量=队列的总容量-已经被占用的空间数量