exchanger 交换的是对象引用而非副本,实现零拷贝的内存指针移交。调用 exchange() 时 jvm 不复制内容、不序列化、不触发 gc,两线程直接互换缓冲区所有权,适用于双缓冲等高性能场景。

Exchanger 不交换对象副本,它只交换引用——这是理解其应用案例的关键前提。所谓“对象副本交换”是常见误解,实际中它实现的是零拷贝的内存指针移交,正因如此,才在双缓冲、图像处理、高频状态同步等场景带来显著内存与性能优势。
为什么不是副本,而是引用移交
调用 exchanger.exchange(buffer) 时,JVM 不复制 byte[] 内容、不序列化对象、不触发 GC 压力。线程 A 传入的 backBuffer 和线程 B 传入的 frontBuffer 在交换后直接互换持有权:A 拿到的是 B 原来持有的同一块内存地址,B 同理。这意味着:
- 1MB 的图像缓冲区交换耗时稳定在纳秒级,与数据大小无关
- 双方必须明确约定缓冲区复用规则(如谁负责清空、重置、是否保留有效数据)
- 若一方误将 received 对象当作自己刚填满的 buffer 使用,会引发逻辑错乱——它来自对方,语义完全不同
双缓冲流水线:典型内存优化案例
以实时视频渲染为例,两个固定线程协作:
- 线程 A(采集/预处理):填满 bufferA → exchange(bufferA) → 立即拿到 bufferB → 清空并复用 bufferB 准备下帧
- 线程 B(渲染/编码):处理完上一轮的 bufferB → exchange(bufferB) → 立即拿到 bufferA → 开始渲染新帧
全程仅需两块缓冲区内存,无额外分配、无深拷贝、无读写锁竞争。相比 BlockingQueue + 全拷贝方案,GC 暂停减少 90% 以上,吞吐提升 2~3 倍。
安全交接的三个实操要点
避免因引用移交特性引发空指针或脏数据:
- 初始化时传哑值而非 null,例如 new byte[0] 或 ByteBuffer.allocateDirect(1),防止 NPE 且便于调试追踪
- 泛型声明两端必须一致:Exchanger
,禁止一个线程用原始类型 Exchanger,否则运行时报 ClassCastException - 交换后立即重置接收方缓冲区状态(如 position=0, limit=capacity),不要依赖“对方已清理”的假设
超时与异常:生产环境不可省略的兜底
裸调 exchange(data) 在线程崩溃、调度延迟或资源泄漏时会导致永久阻塞。正确做法是:
- 统一使用带超时重载:exchanger.exchange(myBuf, 1500, TimeUnit.MILLISECONDS)
- 捕获 TimeoutException 后记录告警、释放当前缓冲区、触发降级流程(如跳过本帧)
- 捕获 InterruptedException 后执行 Thread.currentThread().interrupt(),确保中断信号不被吞没











