在Java里如何实现线程安全的生产者消费者队列_Java并发模式应用说明

老强姑娘_4836

老强姑娘_4836

2026-01-20

187人浏览

原创

BlockingQueue 提供开箱即用的线程安全队列,内置阻塞、唤醒与JMM语义保障;无需手写wait/notify,避免虚假唤醒、死锁等错误;ArrayBlockingQueue适合固定容量场景,LinkedBlockingQueue吞吐更高但有GC压力;关闭需配合结束标记或中断机制。

在java里如何实现线程安全的生产者消费者队列_java并发模式应用说明

用 BlockingQueue 实现开箱即用的线程安全队列

Java 标准库已提供线程安全的生产者消费者实现,无需手写 wait/notify 或锁逻辑。BlockingQueue 接口及其实现类(如 ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue)天然支持阻塞式插入与移除,内部已处理所有可见性、原子性和重入问题。

关键点:

  • put() 和 take() 是阻塞方法:队列满时 put() 阻塞,空时 take() 阻塞,自动唤醒,无需手动同步
  • offer(e, timeout, unit) 和 poll(timeout, unit) 支持超时控制,避免无限等待
  • ArrayBlockingQueue 是有界队列,构造时必须指定容量;LinkedBlockingQueue 默认无界(实际是 Integer.MAX_VALUE),但建议显式设容量以防内存溢出
  • 所有操作均已声明为 synchronized 或使用 ReentrantLock + Condition,JMM 语义完备,不需额外 volatile 修饰
public class ProducerConsumerExample {
    private final BlockingQueue<string> queue = new ArrayBlockingQueue(10);

    public void start() {
        Thread producer = new Thread(() -> {
            for (int i = 0; i  {
            for (int i = 0; i <h3>为什么不要手写 <code>wait/notify</code> 版本</h3>
<p>手动基于 <code>synchronized</code> + <code>wait/notifyAll</code> 实现容易出错,且多数场景下得不偿失。</p>
<p>常见错误现象:</p>
<ul>
<li>忘记在 <code>while</code> 循环中检查条件(用 <code>if</code> 会导致虚假唤醒后直接消费 null 或越界)</li>
<li>
<code>notify()</code> 误用:只唤醒一个线程,但多个消费者/生产者等待时可能造成死锁或饥饿</li>
<li>锁粒度不合理:比如把整个生产逻辑包进 synchronized 块,导致 CPU 密集型操作阻塞其他线程</li>
<li>未处理 <code>InterruptedException</code>,吞掉异常后中断状态丢失,线程无法被优雅关闭</li>
</ul>
<p>除非你正在学习 JVM 线程模型,或需要定制唤醒策略(如按优先级分发),否则没必要绕过 <code>BlockingQueue</code>。</p>
<h3>
<code>LinkedBlockingQueue</code> 与 <code>ArrayBlockingQueue</code> 的性能和行为差异</h3>
<p>二者都满足线程安全,但底层机制和适用场景不同,选错会影响吞吐或内存稳定性。</p><div class="aritcle_card flexRow artxards">
											<div class="artcardd flexRow">
												<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill11083" title="Java调试"><img
														src="https://img.php.cn/upload/skill/001/246/273/6adaf20dbae7c507.jpg" alt="Java调试" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
												<div class="aritcle_card_info flexColumn">
													<a rel="nofollow" href="/xiazai/skill11083" title="Java调试" class="overflowclass">Java调试</a>
													<p class="overflowclass">通过 JDWP 实现 Java 远程调试:连接远程 JVM、设置断点、单步执行代码、查看变量值、获取调用栈。适用于需要调试远程 Java 应用、排查运行时状态、追踪代码执行流程的场景。
术语注解
JDWP:Java Debug Wire Protocol,Java 调试有线协议(Java 远程调试底层通信协议)
remote JVM:远程 Java 虚拟机
step through code:代码单步调试(步入 / 步过 / 步出调试)
inspect variables:变量查看 / 变量检视
stac</p>
												</div>
												<a rel="nofollow" href="/xiazai/skill11083" title="Java调试" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
												</a>
											</div>
										</div>
<p>核心区别:</p>
<ul>
<li>
<code>ArrayBlockingQueue</code>:基于可重入锁 + 单一 <code>Condition</code>,生产与消费者共用同一把锁,适合中低并发、容量固定、对内存占用敏感的场景</li>
<li>
<code>LinkedBlockingQueue</code>:使用两把独立锁(<code>takeLock</code> 和 <code>putLock</code>),生产与消费可并行,吞吐更高;但节点对象带来 GC 压力,且默认无界易 OOM</li>
<li>
<code>size()</code> 行为不同:<code>ArrayBlockingQueue.size()</code> 是 O(1),<code>LinkedBlockingQueue.size()</code> 是 O(n)(需遍历链表),高并发下调用要谨慎</li>
<li>构造时若传入 <code>Comparator</code> 或自定义容量,<code>ArrayBlockingQueue</code> 更轻量;若需动态扩容语义,应考虑 <code>ConcurrentLinkedQueue</code> + 外部阻塞逻辑(但不再满足“阻塞”语义)</li>
</ul>
<h3>如何安全关闭生产者消费者系统</h3>
<p>没有标准的「关闭队列」API,<code>BlockingQueue</code> 本身不管理生命周期。关闭逻辑必须由上层协调,否则可能卡在 <code>take()</code> 或 <code>put()</code> 上。</p>
<p>推荐做法:</p>
<ul>
<li>向队列插入一个特殊结束标记(如 <code>null</code> 或自定义 <code>POISON_PILL</code> 对象),消费者收到后退出循环</li>
<li>使用 <code>Thread.interrupt()</code> 并捕获 <code>InterruptedException</code>,确保线程能响应中断 —— 所有 <code>BlockingQueue</code> 阻塞方法都会响应中断</li>
<li>避免在 finally 块里调用 <code>queue.clear()</code>:多线程下 clear 可能与生产者冲突,且无实际意义</li>
<li>若使用线程池,记得调用 <code>shutdown()</code> + <code>awaitTermination()</code>,而非直接 <code>shutdownNow()</code>(会丢失未处理完的队列项)</li>
</ul>
<p>真正容易被忽略的是:消费者线程退出后,生产者仍在运行且队列未满,程序不会自动终止。必须显式控制生产者生命周期,或引入计数器/信号量配合关闭流程。</p></string>

Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南

相关专题

更多
c语言中null和NULL的区别
c语言中null和NULL的区别

c语言中null和NULL的区别是:null是C语言中的一个宏定义,通常用来表示一个空指针,可以用于初始化指针变量,或者在条件语句中判断指针是否为空;NULL是C语言中的一个预定义常量,通常用来表示一个空值,用于表示一个空的指针、空的指针数组或者空的结构体指针。

2023.09.22

569

3

java中null的用法
java中null的用法

在Java中,null表示一个引用类型的变量不指向任何对象。可以将null赋值给任何引用类型的变量,包括类、接口、数组、字符串等。想了解更多null的相关内容,可以阅读本专题下面的文章。

2024.03.01

1698

6

while的用法
while的用法

while的用法是“while 条件: 代码块”,条件是一个表达式,当条件为真时,执行代码块,然后再次判断条件是否为真,如果为真则继续执行代码块,直到条件为假为止。本专题为大家提供while相关的文章、下载、课程内容,供大家免费下载体验。

2023.09.25

354

5

c++中volatile关键字的作用
c++中volatile关键字的作用

本专题整合了c++中volatile关键字的相关内容,阅读专题下面的文章了解更多详细内容。

2025.10.23

854

12

硬盘接口类型介绍
硬盘接口类型介绍

硬盘接口类型有IDE、SATA、SCSI、Fibre Channel、USB、eSATA、mSATA、PCIe等等。详细介绍:1、IDE接口是一种并行接口,主要用于连接硬盘和光驱等设备,它主要有两种类型:ATA和ATAPI,IDE接口已经逐渐被SATA接口;2、SATA接口是一种串行接口,相较于IDE接口,它具有更高的传输速度、更低的功耗和更小的体积;3、SCSI接口等等。

2023.10.19

3248

3

PHP接口编写教程
PHP接口编写教程

本专题整合了PHP接口编写教程,阅读专题下面的文章了解更多详细内容。

2025.10.17

5529

12

php8.4实现接口限流的教程
php8.4实现接口限流的教程

PHP8.4本身不内置限流功能,需借助Redis(令牌桶)或Swoole(漏桶)实现;文件锁因I/O瓶颈、无跨机共享、秒级精度等缺陷不适用高并发场景。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2025.12.29

3909

9

java接口相关教程
java接口相关教程

本专题整合了java接口相关内容,阅读专题下面的文章了解更多详细内容。

2026.01.19

506

15

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

2023.08.10

4898

6

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
dev.java 官方:Learn Java
dev.java 官方:Learn Java

共0课时 | 0人学习

Java JDBC数据库连接官方教程
Java JDBC数据库连接官方教程

共0课时 | 0人学习