
BlockingQueue
BlockingQueue
是一个支持阻塞操作 的队列接口,位于 java.util.concurrent
包中。它的典型应用就是生产者-消费者模型。
需要首先给队列设置容量
BlockingQueue<String> queue = new ArrayBlockingQueue<>(3); // 容量=3
BlockingQueue 的四组 API
1. 抛异常
- 当队列满时,再
add()
会抛异常; - 当队列空时,再
remove()
会抛异常。
scss
add(e) // 插入元素,成功返回 true,满时抛 IllegalStateException
remove() // 移除队首元素,空时抛 NoSuchElementException
element() // 查看队首元素,空时抛 NoSuchElementException
2. 返回特殊值
- 满了插入失败返回
false
; - 空了移除失败返回
null
。
scss
offer(e) // 插入元素,成功返回 true,满时返回 false
poll() // 移除队首元素,空时返回 null
peek() // 查看队首元素,空时返回 null
3. 一直阻塞
- 满了
put()
会一直阻塞,直到队列有空间; - 空了
take()
会一直阻塞,直到队列有元素。
scss
put(e) // 插入元素,满时阻塞
take() // 移除并返回队首元素,空时阻塞
4.超时退出
- 满了
offer(e, time, unit)
会等待指定时间,超时返回false
; - 空了
poll(time, unit)
会等待指定时间,超时返回null
。
scss
offer(e, 2, TimeUnit.SECONDS) // 等待 2 秒插入,失败返回 false
poll(2, TimeUnit.SECONDS) // 等待 2 秒取出,失败返回 null
操作类型 | 抛异常 | 特殊值 | 阻塞 | 超时 |
---|---|---|---|---|
插入 | add(e) |
offer(e) |
put(e) |
offer(e, time, unit) |
移除 | remove() |
poll() |
take() |
poll(time, unit) |
检查队首 | element() |
peek() |
------ | ------ |
适合场景
- 抛异常:适合严格要求,必须处理异常的场景。
- 返回特殊值:适合可以容忍失败的场景。
- 阻塞:适合生产者-消费者模型,不急着返回,等资源可用。
- 超时:适合对实时性有要求的场景,不能无限等待。
SynchronousQueue
SynchronousQueue
是 Java 并发包里一个 非常特殊的 BlockingQueue 。它和其他 BlockingQueue
(如 ArrayBlockingQueue
、LinkedBlockingQueue
)不一样,它的容量永远是 0,没有任何内部缓冲。
特点
- 容量为 0
-
- 不能存放任何元素。
put()
一个元素时,必须有另一个线程正在take()
,否则会阻塞。take()
时也必须有线程put()
,否则也会阻塞。
- 一手交钱一手交货
-
- 类似一个"当面交易"的模型。
- 生产者线程和消费者线程必须同时到场,才能完成元素的交接。
- 线程间直接传递数据
-
- 没有缓存区,数据不存储,而是 直接在两个线程之间传递
API 行为
put(e)
:如果没有消费者在等待,阻塞。take()
:如果没有生产者在等待,阻塞。offer(e, timeout, unit)
:在超时时间内,如果有消费者等待就成功,否则返回false
。poll(timeout, unit)
:在超时时间内如果有生产者等待就取到数据,否则返回null
。
使用场景
- 线程间直接交换消息
-
- 两个线程需要强同步(例如生产者和消费者一对一交接)。
- 线程池的工作队列(Executors.newCachedThreadPool 默认使用它)
-
- 因为
SynchronousQueue
不存储任务,提交的任务必须马上被某个线程取走执行,如果没有空闲线程,就创建新线程。 - 这也是为什么
CachedThreadPool
会无限制创建线程的原因。
- 因为