发布于2026-07-10 阅读(0)
扫一扫,手机访问
Exchanger 是 Ja va 并发包中一个很有意思的同步工具,专为两个线程一对一交换数据而设计。它适合双缓冲图像处理、分治算法结果合并这类“必须严格配对”的场景。但要注意:它不支持多线程,如果需要超时控制,得显式调用带超时参数的重载方法。另外,引用共享和线程独占使用这两点也容易踩坑。

Exchanger 是 Ja va 并发包里一个轻量级的线程间同步工具,专门用来让两个线程一对一交换数据。它不适合做多线程广播,也不适合当缓冲队列用——只有在满足“两个线程、各自手里有要交换的结果、必须等对方到了才能继续”这个严格条件时,它才真正派上用场。
常见的一个误用场景是拿它替代 BlockingQueue 或 Phaser。比如三个线程协作、或者需要超时控制、又或者一方先完成多次等待另一方——这时候 Exchanger 要么直接阻塞住,要么抛个 InterruptedException 给你看。
那它到底该用在哪?典型的例子:双缓冲图像处理(A 线程渲染第1帧,B 线程显示第2帧;一帧结束立刻交换缓冲区);并行分治算法中左右子任务结果合并前的配对交换。一句话总结:只有在两个线程必须互相等对方结果才能继续的“握手”场景下,才能发挥它的价值。
Exchanger>
,不能一边是 List 一边是 double[]Exchanger 实例的 exchange() 方法,传入各自的计算结果exchange() 是阻塞方法,哪个线程先到就得等着,直到另一方也调用了 exchange() 才能完成交换,然后拿到对方的数据Exchangerex = new Exchanger<>();// 线程 Aint[] resultA = computeStepA();int[] fromB = ex.exchange(resultA); // 阻塞,直到线程 B 也调用 exchange // 线程 Bint[] resultB = computeStepB();int[] fromA = ex.exchange(resultB); // 阻塞,直到线程 A 也调用 exchange
需要注意一点:如果某个线程不小心调了 exchange(null),另一方收到的就是 null —— 不会报错,但后续空指针异常很容易被忽略,调试起来挺头疼。
exchange(V x, long timeout, TimeUnit unit) 是唯一带超时的重载。如果不显式用它,一旦某线程异常退出或逻辑卡住,另一方就会永久阻塞——相当于整个流程被卡死。
TimeoutException,需要捕获并决定是重试、降级还是终止整个流程Thread.interrupt()),会抛出 InterruptedException。此时 Exchanger 的内部状态仍然是干净的,可以安全重用try { int[] other = ex.exchange(myData, 5, TimeUnit.SECONDS);} catch (TimeoutException e) { // 记录日志,考虑 fallback 到单线程模式或抛出业务异常} catch (InterruptedException e) { Thread.currentThread().interrupt(); // 恢复中断状态}
Exchanger 内部基于 CAS 和自旋优化,在低竞争下开销极小,纳秒级搞定。但一旦出现多个线程争抢同一个实例,性能会断崖式下降——因为它本质上只支持一对线程。
exchange() 之前做大量计算或 I/O,否则等待时间不可控;最好只交换轻量结果引用(比如对象指针、数组引用),而不是复制大对象Phaser + 共享容器,会更灵活。Exchanger 强制同步反而会拖慢整体吞吐最容易被忽略的一点:交换完成后,双方拿到的是对方的原始引用,不是副本。如果后续代码修改了这个数组或集合,另一方看到的就是脏数据——所以必须明确约定谁负责读、谁负责写,或者手动深拷贝。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8