参考:
Java
并发编程的艺术
JDK
版本:AdoptOpenJDK 11.0.10+9
1 概念
Exchanger
(交换器),可以用于线程之间交换数据。
它提供一个同步点,在这个同步点两个线程可以交换彼此的数据。这两个线程通过exchange()
方法交换数据, 如果第一个线程先执行exchange()
方法,它会一直等待第二个线程也执行exchange()
方法,当两个线程都到达同步点时,这两个线程就可以交换数据。
因此使用Exchanger
的重点是成对的线程使用exchange()
方法,当有一对线程达到了同步点,就会进行交换数据。
2 方法
Exchanger
提供了一些方法,如下:
方法 | 说明 |
---|---|
V exchange(V x) | 交换数据。V 是要交换的数据类型,一个线程执行了该方法,会等待另一个线程执行该方法后进行数据交换,否则一直等待。 |
V exchange(V x, long timeout, TimeUnit unit) | 带超时时间的方法。如果超时,将不再等待。 |
3 例子
public class ExchangerTest {
public static void main(String[] args) {
Exchanger<Object> exchanger = new Exchanger<>();
Task thread1 = new Task(exchanger, "info from thread1");
Task thread2 = new Task(exchanger, "info from thread2");
// 启动线程
(new Thread(thread1, "thread1")).start();
(new Thread(thread2, "thread2")).start();
}
/**
* 任务类
*/
private static class Task implements Runnable {
private Exchanger<Object> exchanger;
private Object sendObject;
public Task(Exchanger<Object> exchanger, Object sendObject) {
this.exchanger = exchanger;
this.sendObject = sendObject;
}
@Override
public void run() {
try {
// 交换数据
Object receiveObject = exchanger.exchange(sendObject);
// 打印获得的数据
System.out.println(Thread.currentThread().getName()
+ " receive info: " + receiveObject.toString());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
4 源码解析
4.1 构造函数
Exchanger
只有一个构造函数:
public Exchanger() {
participant = new Participant();
}
构造函数内部创建了一个Participant
对象。
查看Participant
源码:
static final class Participant extends ThreadLocal<Node> {
public Node initialValue() { return new Node(); }
}
Participant
继承了ThreadLocal
,用来保存线程本地变量Node
。
查看Node
源码:
@jdk.internal.vm.annotation.Contended static final class Node {
/** 以下字段,多槽交换时使用 */
int index; // Arena 多槽数组的索引
int bound; // 记录上次的bound
int collides; // 当前bound的CAS失败次数
/** 以下字段,单槽交换时使用 */
int hash; // 线程的伪随机数,用于自选优化
Object item; // 当前线程携带的资源
volatile Object match; // 配对线程携带的数据
volatile Thread parked; // 此节点上的阻塞线程
}
可以将Node
理解为线程自身携带的要交换的数据节点对象。
4.2 Exchanger数据交换方式
Exchanger
有两种数据交换方式:
- 单槽位交换(并发量低时选用)
- 多槽位交换(并发量高时选用)
首先,查看exchange()
函数源码:
@SuppressWarnings("unchecked")
public V exchange(V x) throws InterruptedException {
Object v;
Node[] a;
// 转换null参数为空对象
Object item = (x == null) ? NULL_ITEM : x;
// 判断采用哪种交换方式
if (((a = arena) != null ||
(v = slotExchange(item, false, 0L)) == null) &&
((Thread.interrupted() || // disambiguates null return
(v = arenaExchange(item, false, 0L)) == null)))
throw new InterruptedException();
return (v == NULL_ITEM) ? null : (V)v;
}
exchange()
函数的功能就是判断使用哪种交换方式:
- 单槽位交换:如果多槽位数组为空(
arena == null
),采用单槽位交换方式(slotExchange
); - 多槽位交换:如果多槽位数组不为空(
arena != null
),或者单槽位交换失败时,进行多槽位交换(arenaExchange
)。
Exchanger
有两个成员:
private volatile Node[] arena;
private volatile Node slot;
arena
代表多槽数组,在多槽交换时使用;slot
代表单槽交换节点,在单槽交换时使用。
单槽交换、多槽交换源码参考:Java多线程进阶(二一)—— J.U.C之synchronizer框架:Exchanger