Exchanger 是 Java 并发包中专为两个线程一对一交换数据设计的同步工具,适用于双缓冲图像处理、分治算法结果合并等严格配对场景;不支持多线程、超时控制需显式调用带超时重载方法,且须注意引用共享与线程独占使用。

Exchanger 是什么,什么时候该用它
Exchanger 是 Java 并发包中一个轻量级的线程间同步工具,专为两方线程一对一交换数据设计。它不适用于多线程广播或缓冲队列场景,只在严格满足“两个线程、各持一份待交换结果、必须等对方到达才继续”时才真正合适。
常见误用是拿它替代 BlockingQueue 或 Phaser —— 比如三个线程协作、或需要超时控制、或一方先完成多次再等另一方,这时 Exchanger 会直接阻塞住或抛出 InterruptedException。
典型适用场景:双缓冲图像处理(A 线程渲染帧1,B 线程显示帧2;一帧结束立刻交换缓冲区)、并行分治算法中左右子任务结果合并前的配对交换。
基础用法:声明、配对、交换三步不能错
- 声明时指定泛型类型,必须两边一致:
Exchanger<list>></list>,不能一边是 List 一边是 double[]
- 两个线程必须调用同一个
Exchanger 实例的 exchange() 方法,传入各自计算结果
-
exchange() 是阻塞方法,任一线程先到都会等待,直到另一方也调用才完成交换并返回对方的数据
Exchanger<int[]> ex = new Exchanger<>();
// 线程 A
int[] resultA = computeStepA();
int[] fromB = ex.exchange(resultA); // 阻塞,直到线程 B 也调用 exchange
<p>// 线程 B
int[] resultB = computeStepB();
int[] fromA = ex.exchange(resultB); // 阻塞,直到线程 A 也调用 exchange</p>
Exchanger<list>></list>,不能一边是 List 一边是 double[]
Exchanger 实例的 exchange() 方法,传入各自计算结果exchange() 是阻塞方法,任一线程先到都会等待,直到另一方也调用才完成交换并返回对方的数据注意:如果某线程调用 exchange(null),另一方收到的就是 null —— 不会报错,但后续 NPE 很容易被忽略。
立即学习“Java免费学习笔记(深入)”;
超时与中断:不加处理就卡死
exchange(V x, long timeout, TimeUnit unit) 是唯一带超时的重载。不显式使用它,一旦某线程异常退出或逻辑卡住,另一方将永久阻塞。
- 超时后抛出
TimeoutException,需捕获并决定是否重试、降级或终止整个流程 - 若线程被中断(
Thread.interrupt()),会抛出InterruptedException,此时exchanger的内部状态仍保持一致,可安全重用 - 不建议设过短超时(如 10ms),网络抖动或 GC 暂停都可能误触发;推荐按实际计算耗时的 2–3 倍设定
try {
int[] other = ex.exchange(myData, 5, TimeUnit.SECONDS);
} catch (TimeoutException e) {
// 记录日志,考虑 fallback 到单线程模式或抛出业务异常
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 恢复中断状态
}
性能和线程模型:别让它成为瓶颈
Exchanger 内部基于 CAS 和自旋优化,在低竞争下开销极小(纳秒级),但一旦出现“多个线程争抢同一个实例”,性能会断崖式下降——因为它本质只支持一对线程。
- 不要让多个工作线程共用一个
Exchanger实例;应按协作对分配,比如每组双流水线独立一个Exchanger - 避免在
exchange()前做大量计算或 I/O,否则等待时间不可控;最好只交换轻量结果引用(如对象指针、数组引用),而非复制大对象 - 如果算法天然存在“一方快、一方慢”的不对称性,考虑用
Phaser+ 共享容器更灵活,Exchanger强制同步反而拖慢整体吞吐
最常被忽略的一点:交换完成后,双方拿到的是对方的原始引用,不是副本。如果后续代码修改了这个数组或集合,另一方看到的就是脏数据 —— 必须明确约定谁负责读、谁负责写,或手动深拷贝。


















