Exchanger是一种线程间安全交换数据的机制。当线程A调用Exchange对象的exchange()方法后,他会进入阻塞状态,直到线程B也调用了exchange()方法,然后以线程安全的方式交换数据,之后线程A和B继续运行。
这里先提出两个疑问,带着疑问我们分析一下源码:
调试进去,你会发现Exchanger的主要逻辑实现在方法doExchange里面。
private Object doExchange(Object item, boolean timed, long nanos) {
Node me = new Node(item); // Create in case occupying
/**
* index是线程ID的hash值映射到0到max之间的一个值,
* max的定义如下:可知其默认值为0,也就是说index的初始值为0
* private final AtomicInteger max = new AtomicInteger();
*
*/
int index = hashIndex(); // Index of current slot
int fails = 0; // Number of CAS failures
/**
* 这里的无条件的for循环用于自旋
*/
for (;;) {
Object y; // Contents of current slot
/**
* arena是一个Slot数组,初始化语句如下:这里取index=0即第一个元素
* private static final int CAPACITY = 32;
* private volatile Slot[] arena = new Slot[CAPACITY];
*/
Slot slot = arena[index];
/**
* 判断slot是否为null,初始状态arena[0]肯定为null,也就是说初始状态,程序会进入该分支
* 调用createSlot方法创建slot,并赋值给arena的index位置的元素。然后会进入下次循环。
*
*/
if (slot == null) // Lazily initialize slots
createSlot(index); // Continue loop to reread
/**
* 判断slot的值否为null,在非null的情况下(说明该槽里有值,即有其他线程等待交换),然后
* 通过CAS尝试取出该值并把slot(因为slot是AtomicReference类型,值就是其成员value的
* 值)原值赋值为null,CAS操作成功后,当前slot就被释放了,其他线程可以继续使用这个
* slot,当前两个线程通过Node进行交换值。
*/
else if ((y = slot.get()) != null && // Try to fulfill
slot.compareAndSet(y, null)) {
Node you = (Node)y; // Transfer item
/**
* 这里通过CAS交换当前slot对应的两个线程(实际上一个slot同时只能有一个线程占据,这
* 里的意思是一个线程刚好遇到当前slot的值不为null,即有线程等待交换)的值,如果成功
* 则返回交换后的值(这里注意返回的是you.item, you是Node类型,Node是
* AtomicReference类型,you.compareAndSet(null, item)这句代码的作用是CAS替换
* 为null的value值,详情可参考AtomicReference的方法compareAndSet),这一点要特
* 别注意(否则后面的代码很难看懂,特别是spinWait):等待交换的线程把要交换的数据保
* 存在item成员里,而交换线程把要交换的数据保存在value里面;并unpark
* 唤醒交换线程;如果失败(被其他线程抢先了),继续下面的判断,这时如果运气好,坑还没
* 被占(当前slot),则在下一个if分支有机会占坑(只是有机会,运气不好也有可能失
* 败)。
*/
if (you.compareAndSet(null, item)) {
LockSupport.unpark(you.waiter);
return you.item;
} // Else cancelled; continue
}
/**
* 判断当前slot的值y是否为null(即是否有线程占据改slot),如果为null(即没有线程占
* 据),则通过CAS把自己的值赋给当前slot(即尝试占据当前slot),如果CAS操作成功,判断
* 当前索引是否0,如果为0(即说明当前线程所在的slot是整个slot数组的第一个元素),则阻塞
* 等待(timed标示阻塞是否有超时,nanos是阻塞时间);如果CAS操作失败(即当前slot被其他
* 线程占据),
*/
else if (y == null && // Try to occupy
slot.compareAndSet(null, me)) {
if (index == 0) // Blocking wait for slot 0
return timed ?
awaitNanos(me, slot, nanos) :
await(me, slot);
/**
* 所谓的spin wait:就是固定次数循环(详情参考方法spinWait),不同于awaitNanos。
*/
Object v = spinWait(me, slot); // Spin wait for non-0
if (v != CANCEL)
return v;
me = new Node(item); // Throw away cancelled node
int m = max.get();
/**
* 如果spinWait自旋返回CANCEL(即线程没有得到交换的数据被取消,两种可能:一种是被取
* 消,一种是达到自旋次数还未得到交换数据),判断当前最大索引值是否大于当前索引的一半
* 是则把slot数组的最大下标值做减一操作(就好比市场摊位太多,商贩和消费者碰头的概率太
* 低,减少摊位数,增大这个概率)。
*/
if (m > (index >>>= 1)) // Decrease index
max.compareAndSet(m, m - 1); // Maybe shrink table
}
/**
* 每个槽上允许2次失败
*/
else if (++fails > 1) { // Allow 2 fails on 1st slot
int m = max.get();
/**
* CAS失败处理达到3次则增大index,也就是增加CAS的槽的下标(就好比商贩A在市场的当前
* 这个摊位半天没遇到一位顾客,那他肯定觉得这个摊位不发财,要换摊位)。
*/
if (fails > 3 && m < FULL && max.compareAndSet(m, m + 1))
index = m + 1; // Grow on 3rd failed slot
else if (--index < 0)
index = m; // Circularly traverse
}
}
}
private static Object spinWait(Node node, Slot slot) {
/**
* private static final int SPINS = (NCPU == 1) ? 0 : 2000;
* NCPU 表示CPU的核数,
* 所谓的spin wait:就是固定次数循环,每次计数减一对于单核系统来说,spin wait
* 是不做的,因为单核做wait时需要占用CPU,其他线程是无法使用CPU,因此这样的等待
* 毫无意义。而多核系统中spin值为2000,也就是会做2000次循环。如果循环完成后依然
* 没得到交换的数据,那么会返回一个CANCEL对象表示请求依旧被取消,并且把Node从
* slot中清除。详情参考spinWait方法。
*/
int spins = SPINS;
for (;;) {
/**
* 这里需要注意,node.get()返回值是Node(AtomicReference类型)的value,一定要与Node
* 的item区分开,当交换线程来到之前,等待交换的线程自旋获取Node的value,直到value不为
* 空(即交换线程到来并交换了数据,在方法doExchange的第二个if分支中交换线程通过
* you.compareAndSet(null, item),把自己的交换数据item赋值给为null的value)。
*/
Object v = node.get();
if (v != null)
return v;
else if (spins > 0)
--spins;
else
tryCancel(node, slot);
}
}