exchanger仅支持两个线程成对交换,多或少均导致阻塞;exchange()会阻塞直至配对线程调用,超时版本可防卡死但需处理timeoutexception和interruptedexception,且不保证对象可见性。

Exchanger 只能用于两个线程之间成对交换,多一个或少一个都会卡死;它不是队列,也不支持广播或重用配对逻辑。
exchange() 阻塞等待配对线程的典型表现
调用 exchange(V x) 后线程不往下走,CPU 占用低但程序无响应——这不是 bug,是设计使然。只要有一个线程先执行了 exchange(),它就会一直 park 在那里,直到另一个线程也调用同一个 Exchanger 实例的 exchange()。
- 常见错误:启动三个线程,只让其中两个调用
exchange(),第三个线程永远等不到对手,第一个线程也就永远阻塞 - 异常提前退出也会导致“单边到达”:比如线程 A 在
exchange()前抛了NullPointerException,线程 B 就会无限期等待 - 调试建议:在
exchange()前加日志,例如System.out.println("T-" + Thread.currentThread().getId() + " entering exchange"),确认两边都真正执行到了这一步
带超时的 exchange(V x, long timeout, TimeUnit unit) 是防卡死的唯一手段
用 exchange(V x, long timeout, TimeUnit unit) 可避免永久阻塞,但它引入了新问题:超时后抛的是 TimeoutException,不是 InterruptedException;而如果线程在等待时被中断,抛的是 InterruptedException,且中断状态会被清除。
- 必须显式捕获两种异常:
InterruptedException和TimeoutException - 超时后
Exchanger内部状态仍一致,后续调用不受影响;但你的业务逻辑需要决定是重试、降级还是直接失败 - 不要设过短的超时(如 10ms),高负载下线程调度延迟可能触发误超时;建议从 100ms 起步,按实际协作耗时调整
泛型类型必须一致,且不能为 null(除非你明确允许)
Exchanger<string></string> 和 Exchanger<byte></byte> 是完全不同的实例;传入 null 是合法的,但接收方拿到 null 后需自行判断是否可接受——Exchanger 不做空值校验。
- 双缓冲场景中常见写法:
Exchanger<int></int>,线程 A 填满数组后交出,拿到线程 B 刚消费完的空数组;线程 B 同理 - 若某次交换意外传入
null,另一方收到后容易引发后续NullPointerException,建议在exchange()前加非空检查 - 不要混用不同泛型实例:比如用
Exchanger<object></object>接收String和Integer,虽然编译通过,但运行时类型不匹配会导致逻辑错乱
最容易被忽略的一点:Exchanger 不保证“交换后立即可见”。如果交换的是可变对象(比如 ArrayList),双方拿到引用后继续修改,会影响对方——它只交换引用,不深拷贝。需要深拷贝或不可变封装,得你自己来。










