BlockingQueue 是线程安全且带阻塞语义的队列接口,解决普通 Queue 多线程下非安全与忙等问题;支持 put/take 阻塞操作,不接受 null,容量可选,需依场景选 Array 或 Linked 实现,并匹配拒绝策略。

BlockingQueue 是什么,为什么不能直接用普通 Queue
它不是个新概念,而是接口——java.util.concurrent.BlockingQueue,JDK 5 就有了。核心就两点:线程安全、带阻塞语义。普通 ArrayList 或 LinkedList 做队列,多线程往里 add() 或 remove(),不加锁必出错;而 BlockingQueue 所有操作(put()、take()、offer()、poll())天然线程安全,且能主动挂起线程等资源就绪。
常见错误现象:ConcurrentModificationException 频发,或消费者线程空轮询 CPU 占用飙高——这说明你还在用 while(!queue.isEmpty()) { queue.poll(); } 这类非阻塞轮询逻辑。
-
put()满了就阻塞,take()空了就阻塞,省去手动 wait/notify - 不支持 null 元素,插入
null直接抛NullPointerException - 容量可选:有的实现(如
ArrayBlockingQueue)固定大小,有的(如LinkedBlockingQueue)默认 Integer.MAX_VALUE,但别真当无限用——内存爆了不会报“队列满”,而是 OOM
ArrayBlockingQueue vs LinkedBlockingQueue 怎么选
不是性能越快越好,是看你的瓶颈在哪。前者基于数组、固定容量、单锁(所有操作共用一个 ReentrantLock),后者基于链表、可选容量、双锁(putLock 和 takeLock 分离)。
使用场景差异明显:
立即学习“Java免费学习笔记(深入)”;
- 消息量稳定、内存敏感、要求强顺序性 → 选
ArrayBlockingQueue。比如日志收集器,每秒写入 1k 条,不想动态扩容抖动 - 吞吐优先、生产消费速率差异大、允许一定内存开销 → 选
LinkedBlockingQueue。比如 Web 请求异步化,突发流量下生产快、消费慢,双锁能减少竞争 - 别用
LinkedBlockingQueue默认无参构造——它等价于new LinkedBlockingQueue(Integer.MAX_VALUE),OOM 风险藏得深
put/take 和 offer/poll 的行为区别必须搞清
这是最容易踩坑的 API 层面。四个方法两两成对,但语义完全不同:
-
put(E e):阻塞直到成功或被中断,不返回值;offer(E e):立即返回 boolean,成功 true,失败(如队列满)false -
take():阻塞直到取到元素或被中断;poll():立即返回元素或 null(队列空时) - 误用
offer()+ 忙等(while(!queue.offer(x)) Thread.sleep(1))等于自己造了个低效自旋锁,CPU 白烧 - 用
put()/take()时务必处理InterruptedException,不处理会导致线程中断状态丢失,上层调度失灵
和线程池搭配时,拒绝策略怎么配才不丢任务
很多同学把 BlockingQueue 当作线程池的“缓冲区”塞进 ThreadPoolExecutor 构造函数,却忽略:当队列也满了,后续提交的任务会触发拒绝策略——默认是 AbortPolicy,直接抛 RejectedExecutionException。
关键点在于,队列类型和拒绝策略要匹配真实诉求:
- 用
ArrayBlockingQueue+CallerRunsPolicy:适合不允许丢任务、且调用方能承担执行压力的场景(比如后台定时任务) - 用
LinkedBlockingQueue(设合理容量)+DiscardOldestPolicy:适合流式数据、老数据价值低(比如实时行情推送) - 千万别在高并发写入场景中,给线程池配无界队列 +
AbortPolicy——异常会被吞掉,问题表现为“任务提交无声无息消失”
真正难的不是选哪个实现类,是想清楚:队列满时,系统该降级、重试、还是丢弃?这个决策一旦定错,压测时才暴露,就晚了。










