Exchanger 是用于两个线程间一对一同步交换对象的工具,适用于双缓冲渲染、乒乓日志刷盘或线程握手测试;不适用于多线程协作或生产者-消费者模型。

Exchanger 是什么,什么时候该用它
Exchanger 不是线程池、也不是锁,它是个「一对一」的同步点:两个线程必须同时调用 exchange(),才能完成对象交换。它不适用于多个线程协作,也不适合做生产者-消费者模型——那种场景用 BlockingQueue 更稳。
典型场景只有两种:
— 一个线程生成数据,另一个线程处理数据,两者严格配对(比如双缓冲渲染、乒乓式日志刷盘);
— 测试中模拟线程间“握手”行为,验证同步逻辑是否正确。
别把它当 ThreadLocal 用,也别指望它能替代 AtomicReference。它只干一件事:等两个线程撞上,换东西,然后各自继续。
exchange() 调用失败的常见表现
最常遇到的是线程卡死,表现为 CPU 占用低但程序无响应。这不是 bug,是 Exchanger 的设计行为:如果只有一个线程调用了 exchange(),它会一直阻塞,直到另一个线程也来调用。
容易踩的坑包括:
— 启动了三个线程,但只配对调用两次 exchange(),第三个线程永远等不到对手;
— 某个线程抛异常提前退出,没走到 exchange(),另一方就无限等待;
— 在 try-with-resources 或 finally 块里漏掉了 exchange() 调用(比如异常后直接 return)。
调试时可加日志:System.out.println("Thread " + Thread.currentThread().getId() + " entering exchange");,确认两边都真到了这一步。
超时和中断怎么安全退出
exchange(V x, long timeout, TimeUnit unit) 是唯一能避免永久阻塞的办法。但要注意:
— 超时后抛出 TimeoutException,不是 InterruptedException;
— 如果线程在等待时被中断,会抛 InterruptedException,且当前线程的中断状态会被清除;
— 超时或中断后,Exchanger 内部状态仍保持一致,不影响后续调用。
推荐写法是带超时 + 捕获两种异常:
try {<br> result = exchanger.exchange(data, 3, TimeUnit.SECONDS);<br>} catch (TimeoutException e) {<br> // 处理超时,比如重试或降级<br>} catch (InterruptedException e) {<br> Thread.currentThread().interrupt(); // 恢复中断状态<br}
别忽略中断处理——很多框架(如 Spring Batch)靠中断通知线程停止,漏掉这步会导致无法优雅关闭。
泛型类型和 null 值的细节
Exchanger 是泛型类,但类型擦除后实际不校验运行时类型。如果线程 A 传 String,线程 B 传 Integer,编译能过,运行时也不会报错,但接收方强制转型会崩。
null 是合法值,可以交换:
— 线程 A 调用 exchanger.exchange(null),线程 B 调用 exchanger.exchange("done"),双方都能拿到对方传的值;
— 但如果业务逻辑把 null 当作“空信号”,就得小心:谁先到、谁后到、是否允许一方为 null,这些都要提前约定清楚。
还有个小陷阱:Exchanger 内部用 CAS 实现,不依赖锁,所以没有公平性保证。两个线程几乎同时调用 exchange(),谁先被调度到谁先配对,没法控制顺序。
真正难的不是怎么写,而是想清楚:这两个线程是不是真的必须“同时到达”?有没有更松耦合的方式?一旦选了 Exchanger,整个流程就得按它的节奏走——它不迁就你的线程生命周期。










