exchanger 是专为两两线程严格同步交换引用值设计的协作工具,适用于双缓冲日志、乒乓内存池等成对数据交换场景,不支持多线程广播或队列式共享。

Exchanger 是什么,适合解决哪类线程协作问题
它不是用来“广播”或“共享”数据的,而是专为**两两配对、严格同步交换**设计的。典型场景:一个线程生产缓冲区 A,另一个线程消费并填充缓冲区 B,双方在边界点互换引用——比如双缓冲日志写入、乒乓式内存池复用。
常见错误是把它当 BlockingQueue 或 Phaser 用:想让多个线程任意交换,结果线程卡死在 exchange() 上,因为 Exchanger 只匹配成对调用,多出的线程会一直阻塞。
- 必须且仅需两个线程参与一次交换;第三个线程调用
exchange()会等待前两者之一再次进入 - 不支持超时的配对(除非用带
timeout的重载),没有“放弃交换”的内置机制 - 交换的是**引用值**,不是拷贝——双方拿到的是对方传入的同一对象,注意线程安全边界
怎么正确调用 exchange(),参数和返回值意味着什么
exchange() 的签名是 V exchange(V x),传入的是“我手上的数据”,返回的是“对方给我的数据”。它不是 void 方法,返回值必须被接收,否则交换就白做了。
容易忽略的是:即使发生中断,exchange() 也可能已完成交换才抛 InterruptedException。所以不能假设“抛异常 = 没交换”。
立即学习“Java免费学习笔记(深入)”;
- 传
null是合法的,但双方都传null就真交换了个空引用,别误以为是初始化信号 - 如果一方先调用
exchange(v1)进入等待,另一方调用exchange(v2),则前者返回v2,后者返回v1 - 若超时版本
exchange(v, timeout, unit)返回null,只代表超时未配对,不代表对方没调用——可能对方刚进、还没到配对点
Exchanger 在实际代码里怎么避免死锁和资源泄漏
最常踩的坑是:线程在交换前已持有锁,或交换后没清理状态,导致后续逻辑卡住。Exchanger 本身无锁,但它的使用上下文极易引入锁竞争。
例如用 Exchanger 协调两个工作线程交换 ByteBuffer,但忘了重置 position 和 limit,下一轮读写直接越界或跳过数据。
- 交换前后务必做状态重置(如 buffer 的
clear()、集合的clear()),别依赖“对方会清” - 不要在 synchronized 块里调用
exchange(),否则可能造成锁顺序反转+等待链 - 如果某线程因异常退出,没走到下一次
exchange(),它的配对线程就会永久阻塞——需配合Thread.interrupt()或超时机制兜底
Exchanger 和其他同步工具的关键区别在哪
它和 CyclicBarrier 看似都等“人齐”,但语义完全不同:CyclicBarrier 是大家到达后一起往下走;Exchanger 是两人面对面交货,交完各走各路,没有“下一步统一动作”。
也别和 TransferQueue 混:后者支持单向传递、可选阻塞/非阻塞、能排队,而 Exchanger 是硬性双向、强配对、无队列。
-
Exchanger内部用的是 CAS + 自旋 + 队列节点,高争用下比纯锁轻量,但比无竞争的本地变量开销大得多 - Java 9+ 中它仍无结构化并发支持(比如无法绑定
StructuredTaskScope),异常传播需手动处理 - 调试时看不到“谁在等谁”,只能靠日志打点或 JFR 事件跟踪
exchange()调用栈
配对这件事,看着简单,但一旦涉及生命周期管理、异常路径、状态一致性,就很容易漏掉一环。别指望它自动兜底。










