java.util.concurrent*包中的Exchanger类可用于两个线程之间交换信息
可简单地将Exchanger对象理解为一个包含两个格子的容器,通过exchanger方法可以向两个格子中填充信息。当两个格子中的均被填充时,该对象会自动将两个格子的信息交换,然后返回给线程,从而实现两个线程的信息交换。
源码:
构造函数
public Exchanger() {
//Participant 是一个ThreadLocal子类,初始化一个Node节点
participant = new Participant();
}
Node节点:存储交换数据的节点。
- 每个线程都有一个自己的Node,存储在Participant即ThreadLocal对象当中
- Exchanger对象是多个线程共享的,要交换的数据存储在Exchanger.slot的Node成员对象中
/**
* Nodes hold partially exchanged data, plus other per-thread
* bookkeeping. Padded via @sun.misc.Contended to reduce memory
* contention.
*/
@sun.misc.Contended static final class Node {
int index; // Arena 序号,Arena 是一个CAS失败后进行等待的Node[]
int bound; // Arena 数组的最大序列值
int collides; //当前cas失败次数
int hash; // 伪随机自旋 hash值,后面用来自旋
Object item; // 当前线程要交换的值
volatile Object match; // 要被交换线程的值
volatile Thread parked; // 要交换的线程,正在阻塞中。交换完成后设置为Null
}
/** The corresponding thread local class */
static final class Participant extends ThreadLocal<Node> {
public Node initialValue() { return new Node(); }
}
exchange : 等待另一个线程到达这个交换点,然后将给定的对象传输给它,接收它的对象
- 判断arena (CAS失败Node)是否有数据,有的话进行arenaExchange否则slotExchange
- slotExchange处理中发生了CAS失败return null,进行arenaExchange处理
public V exchange(V x) throws InterruptedException {
Object v;
//null值处理
Object item = (x == null) ? NULL_ITEM : x; // translate null args
if ((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;
}
slotExchange :交换的核心逻辑
- 是否有线程要进行交换数据(Exchanger.slot != null),如果有则进行数据交换,唤醒交换线程
- 如果交换数据时CAS操作失败则返回Null,进行arenaExchange处理
- 如果进行等待交换,先进行自旋等待,由SPINS决定次数。自旋结束后再进行park阻塞等待。这时候Exchanger.slot的值就是等待交换线程的Node对象(存储在ThreadLocal)
- 被另一个线程进行唤醒交换数据,执行一系列清空操作,返回已经交换好的数据。交换数据另一个线程已经设置好
private final Object slotExchange(Object item, boolean timed, long ns) {
//取出初始化的node节点
Node p = participant.get();
Thread t = Thread.currentThread();
if (t.isInterrupted()) // preserve interrupt status so caller can recheck
return null;
//这个循环是为了cas失败在再次进行判断。比如有1个线程在等待交换,2个线程同时进入for循环准备交换,同一时刻只能一个成功交换,另一个就要进入arena数组中。
for (Node q;;) {
//slot是上一个线程交换的数据
if ((q = slot) != null) {
//q是上一个线程要交换的node
//如果有线程在等待交换了,则进行交换
if (U.compareAndSwapObject(this, SLOT, q, null)) {
Object v = q.item;
q.match = item;
Thread w = q.parked;
if (w != null)
//将上一个等待交换的线程调用
U.unpark(w);
return v;
}
// create arena on contention, but continue until slot null
//如果上面的compareAndSwapObject CAS操作失败了,则放入arena的竞争节点数组里。有多个线程同时进行交换cas有可能失败的。
if (NCPU > 1 && bound == 0 &&
U.compareAndSwapInt(this, BOUND, 0, SEQ))
arena = new Node[(FULL + 2) << ASHIFT];
}
else if (arena != null)
//进入arenaExchange方法
return null; // caller must reroute to arenaExchange
else {
//线程第一次进来,没有线程要进行交换。自己就准备进行等待交换
p.item = item;
if (U.compareAndSwapObject(this, SLOT, null, p))
break;
p.item = null;
}
}
// await release 等待交换
int h = p.hash;
long end = timed ? System.nanoTime() + ns : 0L;
//自旋次数,NCPU是CPU核数
int spins = (NCPU > 1) ? SPINS : 1;
Object v;
while ((v = p.match) == null) {
//自旋等待
if (spins > 0) {
//通过hash操作来判断自旋,我猜想应该是保证每次交换的节点hash不一样,保证唯一吧。
h ^= h << 1; h ^= h >>> 3; h ^= h << 10;
if (h == 0)
h = SPINS | (int)t.getId();
else if (h < 0 && (--spins & ((SPINS >>> 1) - 1)) == 0)
Thread.yield();
}
//上面已经设置过U.compareAndSwapObject(this, SLOT, null, p)
//所以slot!=p是判断当前要等待交换的节点是不是已经被其它线程交换了。在多线程环境,这种现象是有的。
else if (slot != p)
spins = SPINS;
else if (!t.isInterrupted() && arena == null &&
(!timed || (ns = end - System.nanoTime()) > 0L)) {
U.putObject(t, BLOCKER, this);
p.parked = t;
if (slot == p)
//当前线程阻塞
U.park(false, ns);
p.parked = null;
U.putObject(t, BLOCKER, null);
}
//如果当前节点已经被其它线程交换走,就设置slot为空
else if (U.compareAndSwapObject(this, SLOT, p, null)) {
v = timed && ns <= 0L && !t.isInterrupted() ? TIMED_OUT : null;
break;
}
}
//最后的清空操作
U.putOrderedObject(p, MATCH, null);
p.item = null;
p.hash = h;
//返回另一个线程要交换的数据,v=p.match,另一个线程已经将要交换的数据放入p.match
return v;
}
arenaExchange : CAS失败的交换,也就是arena数组的交换。核心逻辑和slotExchange差不多
- 第一个进入arena数组的Node是等待别人交换,这时候arena是没有数据的。设置arena数据并且进行等待,等待也是先进行自旋等待
- 如果arena有数据,则进行数据交换
private final Object arenaExchange(Object item, boolean timed, long ns) {
Node[] a = arena;
Node p = participant.get();
for (int i = p.index;;) { // access slot at i
int b, m, c; long j; // j is raw array offset
//取出arena的数据node,第一个进入arena数组的Node是等待别人交换,这时候arena是没有数据的
Node q = (Node)U.getObjectVolatile(a, j = (i << ASHIFT) + ABASE);
if (q != null && U.compareAndSwapObject(a, j, q, null)) {
//进行数据交换
Object v = q.item; // release
q.match = item;
Thread w = q.parked;
if (w != null)
U.unpark(w);
return v;
}
else if (i <= (m = (b = bound) & MMASK) && q == null) {
p.item = item; // offer
//第一次进入arena数组,没有数据,等待交换并且添加到arena中
if (U.compareAndSwapObject(a, j, null, p)) {
long end = (timed && m == 0) ? System.nanoTime() + ns : 0L;
Thread t = Thread.currentThread(); // wait
for (int h = p.hash, spins = SPINS;;) {
Object v = p.match;
if (v != null) {
//如果等待的过程中,有线程已经来交换了则进行交换
U.putOrderedObject(p, MATCH, null);
p.item = null; // clear for next use
p.hash = h;
return v;
}
else if (spins > 0) {
//自旋等待
h ^= h << 1; h ^= h >>> 3; h ^= h << 10; // xorshift
if (h == 0) // initialize hash
h = SPINS | (int)t.getId();
else if (h < 0 && // approx 50% true
(--spins & ((SPINS >>> 1) - 1)) == 0)
Thread.yield(); // two yields per wait
}
else if (U.getObjectVolatile(a, j) != p)
spins = SPINS; // releaser hasn't set match yet
else if (!t.isInterrupted() && m == 0 &&
(!timed ||
(ns = end - System.nanoTime()) > 0L)) {
//等待交换
U.putObject(t, BLOCKER, this); // emulate LockSupport
p.parked = t; // minimize window
if (U.getObjectVolatile(a, j) == p)
U.park(false, ns);
p.parked = null;
U.putObject(t, BLOCKER, null);
}
else if (U.getObjectVolatile(a, j) == p &&
U.compareAndSwapObject(a, j, p, null)) {
//一般是线程中断或者超时才会进来
if (m != 0) // try to shrink
U.compareAndSwapInt(this, BOUND, b, b + SEQ - 1);
p.item = null;
p.hash = h;
i = p.index >>>= 1; // descend
if (Thread.interrupted())
return null;
if (timed && m == 0 && ns <= 0L)
return TIMED_OUT;
break; // expired; restart
}
}
}
else
p.item = null; // clear offer
}
else {
if (p.bound != b) { // stale; reset
p.bound = b;
p.collides = 0;
i = (i != m || m == 0) ? m : m - 1;
}
else if ((c = p.collides) < m || m == FULL ||
!U.compareAndSwapInt(this, BOUND, b, b + SEQ + 1)) {
p.collides = c + 1;
i = (i == 0) ? m : i - 1; // cyclically traverse
}
else
i = m + 1; // grow
p.index = i;
}
}
}