Exchanger
(交换者)是一个用于线程间协作的工具类。Exchanger
用于进行线程间的数据交换。它提供一个同步点,在这个同步点,两个线程可以交换彼此的数据。这两个线程通过exchange
方法交换数据,如果第一个线程先执行 exchange()
方法,它会一直等待第二个线程也执行 exchange
方法,当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方。
// 用于左移Node数组下标,以便得出数组在内存中偏移量来获取数据,避免伪共享
private static final int ASHIFT = 7;
// Node数组最大下标
private static final int MMASK = 0xff;
// 用于递增bound,每次递增一个SEQ
private static final int SEQ = MMASK + 1;
// CPU核心数
private static final int NCPU = Runtime.getRuntime().availableProcessors();
// 当前数组最大下标
static final int FULL = (NCPU >= (MMASK << 1)) ? MMASK : NCPU >>> 1;
// 自旋次数,CPU核心为1时自旋会被禁用
private static final int SPINS = 1 << 10;
// 用于exchange方法中参数为null时传递给其它线程的对象
private static final Object NULL_ITEM = new Object();
// 用于超时传递的对象
private static final Object TIMED_OUT = new Object();
// 节点用于保持需要交换的数据
@sun.misc.Contended static final class Node {
int index; // arana数组下标,多槽位时使用
int bound; // 上一次记录的bound
int collides; // CAS失败次数
int hash; // 用于自旋的伪随机数
Object item; // 当前线程需要交换的数据
volatile Object match; // 匹配线程交换的数据
volatile Thread parked; // 记录当前挂起的线程
}
// 用户记录线程状态的内部类
static final class Participant extends ThreadLocal<Node> {
public Node initialValue() { return new Node(); }
}
// 记录线程状态
private final Participant participant;
// 多槽位数据交换使用
private volatile Node[] arena;
// 用于交换数据的槽位
private volatile Node slot;
/**
* The index of the largest valid arena position, OR'ed with SEQ
* number in high bits, incremented on each update. The initial
* update from 0 to SEQ is used to ensure that the arena array is
* constructed only once.
*/
private volatile int bound;
exchange
方法
public V exchange(V x) throws InterruptedException {
Object v;
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;
}
private final Object slotExchange(Object item, boolean timed, long ns) {
Node p = participant.get(); // 获取当前节点对象
Thread t = Thread.currentThread(); // 当前线程
// 线程中断,直接返回null
if (t.isInterrupted()) // preserve interrupt status so caller can recheck
return null;
// 自旋
for (Node q;;) {
// 槽位slot不为null,则说明已经存在线程等待交换数据
if ((q = slot) != null) {
// CAS置空槽位slot
if (U.compareAndSwapObject(this, SLOT, q, null)) {
Object v = q.item; // 获取槽位中需要交换的对象
q.match = item; // 将当前需要交换的数据设置到match中
Thread w = q.parked; // 获取被挂起线程
// 存在挂起线程,则唤醒
if (w != null)
U.unpark(w);
return v; // 返回交换后的数据
}
// 存在竞争,其它线程抢先一步,需要使用多槽位交换方式
// CPU为多核心 且 bound等于0(arana数组未初始化),则CAS操作将bound增加SEQ
if (NCPU > 1 && bound == 0 &&
U.compareAndSwapInt(this, BOUND, 0, SEQ))
arena = new Node[(FULL + 2) << ASHIFT]; // 初始化arana数组
}
else if (arena != null) // 多槽位不为空,执行多槽位交换
return null; // caller must reroute to arenaExchange
else {
// 表示当前线程是第一个线程进来交换数据 或 之前交换任务已完成,可重新认为是第一个线程,
// 将需要交换的数据存放到槽位slot的item属性
p.item = item;
// CAS设置槽位为p
if (U.compareAndSwapObject(this, SLOT, null, p))
break; // CAS操作成功结束自旋
p.item = null; // CAS设置槽位失败,置空item,继续自旋操作
}
}
// 当前线程已经占据槽位,等待其它线程交换数据
int h = p.hash;
long end = timed ? System.nanoTime() + ns : 0L;
int spins = (NCPU > 1) ? SPINS : 1; // 自旋次数
Object v;
// 其它线程成功交换槽位中数据
while ((v = p.match) == null) {
if (spins > 0) { // 自旋
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(); // 线程让步,提供CPU利用率
}
// 存在线程交换数据,已修改槽位slot,但未修改match属性,则等待
else if (slot != p)
spins = SPINS;
// 线程未中断 且 不是多槽位交换 且 (没有设置超时 或 超时时间未到)
else if (!t.isInterrupted() && arena == null &&
(!timed || (ns = end - System.nanoTime()) > 0L)) {
U.putObject(t, BLOCKER, this); // 设置线程t被当前对象阻塞
p.parked = t; // 设置节点挂起线程属性parked
// 如果槽位slot不等于null,表明还没有线程与之交换数据,则将当前线程挂起
if (slot == p)
U.park(false, ns);
p.parked = null; // 线程被唤醒,将节点挂起线程parked属性设置为null
U.putObject(t, BLOCKER, null); // 设置线程t没有被任何对象阻塞
}
// 不满足上述添加,交换失败,重置槽位slot
else if (U.compareAndSwapObject(this, SLOT, p, null)) {
v = timed && ns <= 0L && !t.isInterrupted() ? TIMED_OUT : null;
break;
}
}
U.putOrderedObject(p, MATCH, null); // 置空match
p.item = null; // 置空item
p.hash = h;
return v; // 返回交换数据
}