阻塞队列BlockingQueue
一、BlockingQueue 是什么?
自带阻塞功能的线程安全队列
- 队空:取数据的线程会阻塞等待
- 队满:放数据的线程会阻塞等待
- 天然实现生产者 - 消费者模型,完全不用写 wait/notify
核心方法(必须记住):
put(e):队满则阻塞take():队空则阻塞offer(e)/poll():不阻塞,返回布尔 /null
二、四大常用阻塞队列(本质区别)
1. ArrayBlockingQueue(有界数组队列)
- 底层:数组
- 边界:必须指定容量,有界
- 锁:一把锁(入队、出队共用同一把 ReentrantLock)
- 特点:简单、公平 / 非公平可选、内存连续
- 场景:需要严格限制队列大小,防止无限排队
1 | |
2. LinkedBlockingQueue(无界 / 有界链表队列)
底层:单向链表
边界:可指定容量;不指定就是 Integer.MAX_VALUE(近似无界)
锁:两把锁
takeLock(取数据)
putLock(放数据)
→ 入队出队可并发,性能更高
场景:线程池默认使用,吞吐量高
1 | |
3. SynchronousQueue(不存储元素的队列)
容量 = 0
不存数据,生产者必须等消费者,消费者必须等生产者
相当于直接交换数据,不经过缓冲区
锁:默认无锁(CAS 实现)
场景:
任务不排队,直接交给线程执行
→ CachedThreadPool 用的就是它
1 | |
4. PriorityBlockingQueue(优先级队列)
- 底层:数组(最小堆实现)
- 边界:无界(自动扩容)
- 特点:元素按优先级出队,不是 FIFO
- 场景:需要按优先级处理任务
三、阻塞队列的线程安全是怎么实现的?
全部基于:ReentrantLock + Condition
底层模板几乎一样:
1 | |
take () 阻塞流程:
- 加锁
- 如果队空 →
notEmpty.await()阻塞等待 - 被唤醒 → 取数据
- 唤醒正在等待 put 的线程
notFull.signal() - 解锁
put () 阻塞流程:
- 加锁
- 如果队满 →
notFull.await()阻塞等待 - 被唤醒 → 放数据
- 唤醒正在等待 take 的线程
notEmpty.signal() - 解锁
一句话:锁保证线程安全,Condition 实现精准阻塞唤醒。
四、阻塞队列与线程池如何配合?
线程池运行流程 = 阻塞队列的真实使用场景
任务进来 → 核心线程数未满?
→ 创建核心线程执行
核心线程满了 →
→ 任务进入阻塞队列排队
队列满了 →
→ 创建非核心线程
最大线程也满了 →
→ 拒绝策略
线程池与队列对应关系
1. FixedThreadPool(固定线程数)
1 | |
- 无界队列
- 任务无限排队
- 不会创建非核心线程
- 风险:OOM
2. CachedThreadPool(缓存线程池)
1 | |
- 队列容量 = 0
- 任务不排队
- 来一个任务创建一个线程
- 适合:大量短任务
3. SingleThreadExecutor(单线程)
1 | |
- 所有任务排队,单线程串行执行
五、4 个阻塞队列一句话总结
- ArrayBlockingQueue:数组、有界、一把锁、公平可选
- LinkedBlockingQueue:链表、有界 / 无界、两把锁、线程池默认
- SynchronousQueue:不存数据、直接交换、CachedThreadPool 用
- PriorityBlockingQueue:优先级、无界、自动扩容
六、最核心的结论
- 阻塞队列 = 线程安全 + 自动阻塞 / 唤醒
- 底层 = ReentrantLock + Condition
- 线程池的排队、阻塞、复用,全靠阻塞队列实现
总结
- BlockingQueue:自带阻塞的线程安全队列,用于生产者 - 消费者模式。
- Array:数组、有界、一锁;Linked:链表、无界、双锁、高性能。
- SynchronousQueue:不存数据,直接交换,用于 CachedThreadPool。
- 线程安全:靠 ReentrantLock + Condition 实现。
- 线程池:依靠阻塞队列实现任务排队。
阻塞队列BlockingQueue
http://hanqichuan.com/2026/04/16/java并发/阻塞队列BlockingQueue/