Exchanger 是什么?它适合什么场景?
简化版
Exchanger(交换器)是一个用于「两个线程之间交换数据」的同步工具——两个线程各自调 exchange(自己的数据),会互相阻塞等待对方到达,然后交换彼此的数据(线程 A 把数据给 B、B 把数据给 A,一手交换)。它是「成对的、点对点的」同步——必须恰好两个线程配对(不是多个线程协同)。典型场景:「生产者-消费者」的一种特殊形式——生产者填满一个缓冲区、消费者清空一个缓冲区,两者交换缓冲区(生产者拿到空的继续填、消费者拿到满的继续清),实现「缓冲区复用、零拷贝传递」。它用得很少(大多数场景用 BlockingQueue 就够),是个小众但巧妙的工具。
详细版
基本用法:两线程交换数据:
Exchanger<String> exchanger = new Exchanger<>();
// 线程 A
new Thread(() -> {
String dataA = "来自A的数据";
String received = exchanger.exchange(dataA); // 给出 A 的数据,阻塞等待 B
// received = "来自B的数据"(拿到了 B 的数据)
}).start();
// 线程 B
new Thread(() -> {
String dataB = "来自B的数据";
String received = exchanger.exchange(dataB); // 给出 B 的数据,阻塞等待 A
// received = "来自A的数据"(拿到了 A 的数据)
}).start();
// 结果:A 和 B 交换了数据(A 拿到 B 的、B 拿到 A 的)
核心特点:
| 特点 | 说明 |
|---|---|
| 参与者 | 恰好两个线程(点对点、成对) |
| 行为 | 互相阻塞等待,到齐后交换数据 |
| 交换内容 | 双向交换(各自把数据给对方) |
| 阻塞 | exchange() 阻塞直到对方到达(或超时/中断) |
| 可重用 | 是(同一个 Exchanger 可反复交换) |
经典场景:缓冲区交换(生产者-消费者):
Exchanger<List<Data>> exchanger = new Exchanger<>();
// 生产者:填满缓冲区后,和消费者交换(拿到空的继续填)
new Thread(() -> {
List<Data> buffer = new ArrayList<>();
while (true) {
buffer.add(produce());
if (buffer.size() == CAPACITY) {
buffer = exchanger.exchange(buffer); // 交换:给出满的、拿到空的
}
}
}).start();
// 消费者:清空缓冲区后,和生产者交换(拿到满的继续消费)
new Thread(() -> {
List<Data> buffer = new ArrayList<>();
while (true) {
buffer = exchanger.exchange(buffer); // 交换:给出空的、拿到满的
for (Data d : buffer) consume(d);
buffer.clear();
}
}).start();
⚠️ Exchanger 只能用于「恰好两个线程」的配对交换——如果只有一个线程调
exchange(),它会一直阻塞(等不到配对的对方);如果三个线程调,会「两两配对」(第三个等下一个)。所以它是「点对点」不是「多方协同」(多方协同用 CyclicBarrier/Phaser)。而且它用得很少——大多数「线程间传递数据」用BlockingQueue更通用(不限两个线程、不用配对)。
完整版教学
一、Exchanger 是什么:两线程交换数据
Exchanger 是一个「点对点、双向的数据交换」工具——两个线程在一个「交换点」相遇,交换彼此的数据:
Exchanger 的核心行为:
线程 A 调 exchange(dataA) → 阻塞,等对方
线程 B 调 exchange(dataB) → 到达,两者交换:
A 拿到 dataB、B 拿到 dataA
→ 两个线程"一手交换",各自拿到对方的数据
关键:
① 必须两个线程配对(一个人调 exchange 会一直等)
② 双向交换(不是单向传递,是互相给)
③ 到齐才交换(都调了 exchange 才交换、否则阻塞等待)
Exchanger 和其他同步工具的区别:它是「点对点、成对、双向交换」——CountDownLatch/CyclicBarrier/Phaser 是「多方协同」(多个线程在一个点集合),而 Exchanger 是「恰好两个线程配对、交换数据」。它更像「两个人碰面交换东西」。理解「Exchanger 是两个线程点对点双向交换数据、必须配对、到齐才换」,就理解了它的独特定位——它是「二人交换」而非「多人集合」。
二、和 BlockingQueue 的对比
Exchanger 常和 BlockingQueue(尤其 SynchronousQueue)对比——它们都能「线程间传递数据」,但方式不同:
BlockingQueue(如 SynchronousQueue):单向传递
生产者 put(data) → 消费者 take() 拿到 data
→ 单向:生产者给、消费者拿(消费者不用给回什么)
Exchanger:双向交换
A exchange(dataA) ←→ B exchange(dataB)
→ 双向:A 给 B 数据、同时 B 给 A 数据(互相交换)
关键区别:
BlockingQueue 是"传递"(一方给、一方拿,单向)
Exchanger 是"交换"(双方互给,双向)
核心区别是「单向传递 vs 双向交换」——BlockingQueue 是「一方给、另一方拿」(消费者不用给回东西),Exchanger 是「两方互相给」(各自拿到对方的)。所以 Exchanger 适合「双方都有数据、要交换」的场景(如缓冲区交换:生产者给满缓冲区、拿回空缓冲区);BlockingQueue 适合「一方产一方消」的单向场景。理解「Exchanger 双向交换、BlockingQueue 单向传递」,就知道了它们的适用差异——Exchanger 的独特价值在「双向」。
三、经典场景:缓冲区交换
Exchanger 最经典的应用是「缓冲区交换(buffer swapping)」——生产者和消费者交换缓冲区,实现缓冲区复用:
缓冲区交换的思路:
生产者:有一个缓冲区,填满后需要一个空的继续填
消费者:有一个缓冲区,清空后需要一个满的继续消费
→ 生产者的"满缓冲区"正好是消费者要的、消费者的"空缓冲区"正好是生产者要的
→ 用 Exchanger 交换:生产者给出满的、拿到空的;消费者给出空的、拿到满的
好处:
① 缓冲区复用(两个缓冲区来回交换,不用不断 new)
② 零拷贝传递(交换的是缓冲区的引用,不复制数据)
③ 天然的流控(缓冲区没满/没空就不交换,节奏自然对齐)
这个场景完美利用了 Exchanger 的「双向交换」——生产者和消费者各持一个缓冲区,交换后各自拿到自己需要的(生产者拿到空的继续填、消费者拿到满的继续消费)。相比「每次 new 一个新缓冲区传递」,交换复用了两个缓冲区(省内存、省 GC)。这是 Exchanger 的招牌用法。理解「缓冲区交换用 Exchanger 让生产者消费者交换满/空缓冲区、复用缓冲区、零拷贝」,就掌握了它最经典的应用——这也是面试问 Exchanger 场景时的标准答案。
四、其他方法与注意点
Exchanger 的方法和注意点:
// 基本交换(无限阻塞直到对方到达)
V exchange(V x) throws InterruptedException;
// 带超时的交换(超时抛 TimeoutException)
V exchange(V x, long timeout, TimeUnit unit)
throws InterruptedException, TimeoutException;
注意点:① exchange() 会阻塞直到对方到达(或被中断抛 InterruptedException);② 带超时版本避免「对方一直不来导致永久阻塞」(超时抛 TimeoutException);③ 只支持两个线程配对——单个线程调会永久阻塞(除非有超时);多个线程调会「两两配对」(不保证谁和谁配);④ 可重用(同一个 Exchanger 可反复用于多次交换);⑤ 交换的数据可以是 null(Exchanger 允许交换 null)。实践中要注意「保证恰好两个线程配对」——如果配对逻辑出错(如一个线程挂了、另一个永久等待),会导致线程卡死,所以生产环境用带超时的版本更安全。理解「exchange 阻塞等对方、有超时版本、只支持两线程配对、可重用」,就掌握了它的 API 和注意点。
五、为什么用得少
Exchanger 虽然巧妙,但实际用得很少,原因值得理解:
Exchanger 用得少的原因:
① 限制严格:只支持"恰好两个线程"配对——大多数场景是"多生产者多消费者",
Exchanger 的"两两配对"不适用
② BlockingQueue 更通用:
- 不限线程数(多个生产者/消费者都行)
- 不用严格配对(谁 put 谁 take 都行)
- 天然的缓冲、流控
→ 大多数"线程间传递数据"用 BlockingQueue 就够,且更灵活
Exchanger 只在"恰好两个线程、且要双向交换"的特定场景才有优势
(如缓冲区交换,且只有一个生产者一个消费者)
核心:Exchanger 的「必须两个线程配对 + 双向交换」限制太严,大多数并发场景(多生产多消费、单向传递)用 BlockingQueue 更合适。它只在「恰好一个生产者一个消费者、且要双向交换缓冲区」这种特定场景才有独特价值。所以它是「小众工具」——知道它的存在和「双向交换、缓冲区交换」的用途即可,实际编码几乎用 BlockingQueue。理解「Exchanger 限制严(两线程配对+双向)、大多数场景 BlockingQueue 更通用、只在特定的两线程双向交换才用」,就知道了它的定位——冷门但在特定场景巧妙。
六、和其他同步工具的定位
把 Exchanger 放进 JUC 同步工具体系,理清各自定位:
| 工具 | 参与者 | 作用 |
|---|---|---|
| CountDownLatch | 多个 | 一个/多个线程等一组任务完成(一次性) |
| CyclicBarrier | 多个(固定) | 一组线程反复集合、齐步走 |
| Phaser | 多个(可变) | 多阶段协同、动态参与者 |
| Semaphore | 多个 | 控制同时访问的数量(限流) |
| Exchanger | 恰好 2 个 | 两线程点对点双向交换数据 |
Exchanger 在这个体系里是最特殊的——唯一「恰好两个线程」的、「交换数据」的工具(其他都是「多方协同」或「计数」)。它填补了「两线程双向交换数据」这个细分需求。理解各工具的定位:等一组完成用 CountDownLatch、反复集合用 CyclicBarrier、多阶段动态用 Phaser、限流用 Semaphore、两线程交换用 Exchanger、线程间传递用 BlockingQueue。理解「Exchanger 是 JUC 里唯一的两线程双向交换工具、和其他多方协同/计数工具定位不同」,就把它在同步工具体系里的位置理清了。
记忆钩子:「Exchanger 是两个线程点对点双向交换数据的工具——两线程各调 exchange(自己的数据)、互相阻塞等对方、到齐后交换(A 拿 B 的、B 拿 A 的);必须恰好两个线程配对(一个人调会永久阻塞);和 BlockingQueue 的区别:Exchanger 双向交换、BlockingQueue 单向传递;经典场景缓冲区交换(生产者给满的拿空的、消费者给空的拿满的,复用缓冲区零拷贝);用得很少(限制严、大多数用 BlockingQueue 更通用),只在两线程双向交换时用」。
七、常见误区与追问
- 误区:Exchanger 能用于多个线程协同。 只支持恰好两个线程配对交换——多个线程调会两两配对、单个线程调会永久阻塞;多方协同用 CyclicBarrier/Phaser。
- 误区:Exchanger 和 BlockingQueue 一样。 Exchanger 是双向交换(两方互相给数据),BlockingQueue 是单向传递(一方给一方拿);Exchanger 适合双方都有数据要交换的场景。
- 误区:一个线程调 exchange 就能拿到数据。 会永久阻塞——exchange 必须等到「对方也调了 exchange」才交换;单个线程没有配对对象,会一直等(除非用带超时的版本)。
- 误区:Exchanger 用得很多。 用得很少——限制严(两线程配对+双向),大多数并发传递数据用 BlockingQueue 更通用;只在「恰好两线程双向交换缓冲区」等特定场景才用。
- 追问:Exchanger 和 BlockingQueue 的区别? Exchanger 是两个线程点对点的双向交换(各自把数据给对方、都拿到对方的),BlockingQueue 是单向传递(生产者 put、消费者 take,消费者不用给回);Exchanger 限恰好两线程、BlockingQueue 不限线程数更通用。
- 追问:Exchanger 的经典应用场景? 缓冲区交换——生产者填满一个缓冲区、消费者清空一个缓冲区,两者用 exchange 交换(生产者给满的拿空的、消费者给空的拿满的),复用两个缓冲区、零拷贝传递、天然流控。
- 追问:Exchanger 支持超时吗?为什么需要? 支持——
exchange(x, timeout, unit);需要是因为如果对方线程一直不来(如挂了),无超时的 exchange 会永久阻塞导致线程卡死,超时版本能避免永久等待。
八、加强记忆
Exchanger(交换器)是用于「两个线程之间点对点、双向交换数据」的同步工具——两个线程各调 exchange(自己的数据),互相阻塞等待对方到达、到齐后交换彼此的数据(A 拿到 B 的、B 拿到 A 的),必须恰好两个线程配对(单个线程调会永久阻塞、多个线程调会两两配对)。它和 BlockingQueue 的关键区别:Exchanger 是「双向交换」(两方互相给),BlockingQueue 是「单向传递」(一方 put 一方 take)。经典应用是「缓冲区交换」——生产者填满一个缓冲区、消费者清空一个缓冲区,两者交换(生产者给出满的拿到空的继续填、消费者给出空的拿到满的继续消费),实现缓冲区复用、零拷贝传递、天然流控。方法:exchange(x)(阻塞等对方)、exchange(x, timeout, unit)(带超时,避免对方不来永久阻塞,生产更安全)。它用得很少——限制严(恰好两线程 + 双向),大多数「线程间传递数据」用 BlockingQueue 更通用(不限线程数、不用配对),只在「恰好一生产者一消费者、双向交换缓冲区」等特定场景才用。它是 JUC 里唯一的「两线程双向交换」工具(其他都是多方协同或计数)。一句话「Exchanger 两线程点对点双向交换数据(必须配对、到齐才换)、区别于 BlockingQueue 单向传递、经典场景缓冲区交换复用、用得很少大多数用 BlockingQueue」。