Exchanger 是 Java 并发包中专为两个线程配对交换数据设计的同步工具,保证原子性与即时性,仅支持两两配对、交换对象引用、无缓存无复制,适用于双缓冲等对等协作场景。

Exchanger 是 Java 并发包中专为两个线程“面对面”交换数据设计的同步工具,不是队列、不是锁、也不是信号量,而是一种轻量级的配对协作机制。它天然保证交换的原子性与即时性——双方同时到场,一手交数据、一手接数据,不缓存、不排队、不复制内容,特别适合双缓冲、流水线双阶段、前后台协同等严格对等的场景。
Exchanger 的核心行为:必须两人“碰头才换”
调用 exchanger.exchange(data) 的线程会立即挂起,直到另一个线程也调用同一实例的该方法。此时二者被唤醒,互换引用,各自拿到对方传来的对象后继续执行。
- 不是“先发后收”,而是“同时交、同时拿”——没有先后顺序,只有配对成功才解除阻塞
- 单线程调用会无限等待(除非设超时),三个及以上线程共用时,仅两两配对,其余持续阻塞
- 交换的是对象引用,不是深拷贝,对大缓冲区(如 byte[])非常友好,零内存冗余
- 泛型类型必须一致,编译期检查;运行时允许 null,但需业务逻辑自行防御
典型安全使用模式:双缓冲轮换示例
以图像渲染为例:一个线程填充后台缓冲区,另一个线程显示前台缓冲区,两者通过 Exchanger 交接缓冲区引用,避免读写冲突和频繁分配。
- 声明:Exchanger
exchanger = new Exchanger(); - 线程 A(渲染)填满 backBuffer 后调用 exchanger.exchange(backBuffer)
- 线程 B(显示)用完 frontBuffer 后调用 exchanger.exchange(frontBuffer)
- 交换完成后,A 拿到刚被显示完的 frontBuffer 继续绘制,B 拿到刚渲染好的 backBuffer 准备显示
必须注意的实战细节
看似简单,但几个关键点直接决定是否稳定可用:
- 永远优先用带超时的版本:exchange(data, 3, TimeUnit.SECONDS),防止因某线程异常退出导致另一方永久挂起
- 捕获 InterruptedException 后恢复中断状态:Thread.currentThread().interrupt(),避免掩盖中断信号
- 不要复用已交换的对象做本地判断:返回值是对方的数据,不是自己传入的原值,常见错误是误把 received 当作本地处理结果
- 公平模式慎开:构造时传 true 虽能按等待时长排序,但会引入队列开销,吞吐通常下降 10%~20%
为什么不用 BlockingQueue 或 SynchronousQueue?
Exchanger 在双线程配对场景下优势明显:
- 内存恒定:只维持两个引用,无内部队列或缓冲节点
- 语义清晰:“我要和对面那个线程换东西”,代码意图一目了然
- 无竞争路径:内部基于 CAS + LockSupport 实现,高并发下延迟更平稳
- 零拷贝:交换引用而非复制内容,对 MB 级缓冲区尤其关键

















