Exchanger 是 Java 并发包中专用于两个线程在屏障点互换数据的同步工具,适合严格配对的双向数据交换场景;它不支持多线程协作,每次 exchange() 需两线程同时调用,否则阻塞,且交换原子完成。

Exchanger 是什么,它适合解决哪类问题
Exchanger 是 Java 并发包(java.util.concurrent)中一个专门用于成对线程同步交换数据的工具类。它不是通用的线程间通信机制,而是为“两个线程在某个屏障点互换数据”这一特定场景设计的——比如一个线程生成数据、另一个线程消费数据,二者必须严格配对、一一交换。
常见误用是把它当 BlockingQueue 或 SynchronousQueue 用:后者支持多生产者/多消费者,而 Exchanger 只允许两两配对;一旦某线程调用 exchange() 后没等到另一个线程,就会一直阻塞(除非指定超时)。
基本用法:调用 exchange() 前后要注意什么
Exchanger 的核心方法只有 exchange(V x) 和带超时的 exchange(V x, long timeout, TimeUnit unit)。它不维护内部状态,也不缓存数据,每次调用都是一次独立的“握手-交换”动作。
- 两个线程必须各自调用一次
exchange(),才能完成一次交换;单方面调用会无限期等待(或超时失败) - 传入的参数
x是当前线程“拿出”的数据,返回值是对方线程“给出”的数据 - 类型
V在两个线程中必须一致(编译期类型安全),但实际运行时可以是不同实例甚至 null - 如果线程在等待时被中断,会抛出
InterruptedException,此时交换未发生
Exchanger<String> ex = new Exchanger<>(); // 线程 A String aData = "from-A"; String fromB = ex.exchange(aData); // 阻塞,直到线程 B 也调用 exchange <p>// 线程 B String bData = "from-B"; String fromA = ex.exchange(bData); // 此刻 A 解除阻塞,双方拿到对方数据</p>
容易踩的坑:超时、中断、线程生命周期不匹配
Exchanger 对线程协作的“对称性”极其敏感,现实代码中常因以下原因失败:
- 一个线程提前退出(如异常终止、return),另一个线程在
exchange()处永久挂起 → 必须配合超时使用 - 调用
exchange()前线程已被中断,但没检查Thread.interrupted(),导致进入阻塞后立即抛InterruptedException - 用同一个
Exchanger实例反复配对不同线程(比如线程池中复用),但无法保证“上次的 A 是否还在等 B” →Exchanger不识别线程身份,只认“当前阻塞的两个”
建议做法:
- 总是使用带超时的
exchange(x, 3, TimeUnit.SECONDS),并捕获TimeoutException - 在
catch (InterruptedException e)中恢复中断状态:Thread.currentThread().interrupt() - 避免在线程池中复用
Exchanger,除非能确保每次只涉及固定两个 worker(例如双工处理管道)
和 SynchronousQueue 比较:什么时候该选 Exchanger
两者都能实现“一进一出”,但语义和约束完全不同:
-
SynchronousQueue是无缓冲队列,支持任意数量的生产者/消费者,但要求 put 和 take 必须“即时配对”;如果只有两个线程且明确知道彼此角色,Exchanger更直白 -
Exchanger强制双向数据流动(A 给 B 数据,B 也必须给 A 数据),而SynchronousQueue是单向传递(A put,B take,B 不需要回传) -
Exchanger的交换是原子的:A 的exchange()返回瞬间,B 的也返回,不存在中间态;SynchronousQueue的 put/take 虽也阻塞,但没有这种“双方同时完成”的语义保证
如果你的场景本质是“线程 A 和线程 B 各自准备一份数据,在某个点互换并继续执行”,而不是“一个推、一个拉”,那 Exchanger 就是更贴切的选择。
真正难的不是写对那两行 exchange(),而是确保两个线程在正确的时间、以正确的顺序、带着正确的状态走到那个交换点——这往往需要额外的状态协调或超时兜底,不能只靠 Exchanger 自身。

















