BlockingQueue 是 java.util.concurrent 包中的接口,实现了 Collection 和 Queue 接口,提供了 3 类操作。
BlockingQueue 操作
Throws exception | Special value | Blocks | Times out | |
---|---|---|---|---|
Insert | add(e) | offer(e) | put(e) | offer(e, time, unit) |
Remove | remove() | poll() | take() | poll(time, unit) |
Examine | element() | peek() | not applicable | not applicable |
和普通的 queue 相比,BlockingQueue 满时 put 元素和 BlockingQueue 为空时 take 元素都会阻塞。下面我们就以 LinkedBlockingQueue 为例来看看具体实现。
首先在 LinkedBlockingQueue 中定义了一些属性。
通过这些属性定义,我们大概可以猜到元素在是以 Node 的形式保存在队列中,并且入队和出队操作都会用锁,元素个数也是通过 AtomicInteger 来保存,所以 LinkedBlockingQueue 是一个线程安全的队列。
接下来我们主要看看两个 block 操作:
- put(e)
put() 中有 3 处判断:
1) 当元素个数小于 capacity 则继续执行,如果等于 capacity 则执行阻塞 notFull 线程。
2) 添加完元素之后,如果元素个数小于 capacity 则唤醒 notFull 线程。
3) 释放锁之前元素个数为 c+1 ,如果 c==0 那么说明队列中有一个元素,所以唤醒 notEmpty 线程。 - take()
和 put(e) 类似,take() 方法也是有 2 处判断:
1) 当元素个数等于 0 ,则阻塞 notEmpty 线程。
2) 当元素个数大于 0 ,则唤醒 notEmpty 线程。
3) take 元素个数会减少,在释放锁之前做了 take 操作,所以释放锁之后队列实际是小于 capacity 的,所以唤醒 notFull 线程。
总结一下:
- LinkedBlockingQueue 有两把锁 putLock 和 takeLock ,有两个 condition 条件 notFull 和 notEmpty 。
- 元素添加或删除操作先获取锁,再操作链表。
- 如果队列满时阻塞 notFull 线程,反之则唤醒 notFull 线程。
- 如果队列中没有元素时,阻塞 notEmpty 线程,反之则唤醒 notEmpty 线程。