按关键词阅读:
<<(31-ASHIFT)) 。* 上限255(0xff)足以满足主要算法的预期缩放比例限制 。*/private static final int MMASK = 0xff;/** * 绑定字段的序列/版本位的单位 。* 每次成功更改边界也会添加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;/** * 等待比赛时旋转的界限 。* 由于随机化 , 实际的实际迭代次数平均约为该值的两倍 。注意:当NCPU == 1时 , 禁用旋转 。*/private static final int SPINS = 1 << 10;/** * 表示空参数/从公共方法返回的值 。* 因为API最初不允许使用null参数 , 所以需要它 。*/private static final Object NULL_ITEM = new Object();/** * 内部交换方法在超时时返回的前哨值 , 以避免需要这些方法的单独定时版本 。*/private static final Object TIMED_OUT = new Object();这几个变量在 LinkedTransferQueue 也有类似的 。
Node 节点相关/** * 节点保存部分交换的数据 , 以及其他每个线程的簿记(bookkeeping) 。*/@sun.misc.Contended static final class Node {//竞技场索引int index;// Exchanger.bound的最后记录值int bound;//当前限制下的CAS失败次数int collides;//伪随机旋转int hash;//该线程的当前元素Object item;//释放线程提供的元素volatile Object match;//停放时设置为此线程 , 否则为nullvolatile Thread parked; }/** 对应线程本地类 ** 这里定义的 ThreadLocal 类 , 用于避免并发争用 。 */static final class Participant extends ThreadLocal
@sun.misc.Contended这个注解是干什么的?
这个主要是用来避免伪共享的 。
这里先简单的解释一下 。
伪共享伪共享 , 高速缓存与内存之间是以缓存行为单位交换数据的 , 根据局部性原理 , 相邻地址空间的数据会被加载到高速缓存的同一个数据块上(缓存行) , 而数组是连续的(逻辑 , 涉及到虚拟内存)内存地址空间 , 因此 , 多个slot会被加载到同一个缓存行上 , 当一个slot改变时 , 会导致这个slot所在的缓存行上所有的数据(包括其他的slot)无效 , 需要从内存重新加载 , 影响性能 。
所以 , 为了避免这种情况 , 需要填充数据 , 使得有效的slot不被加载到同一个缓存行上 。
填充的大小即为1 << 7 , 如下图所示
文章插图
填充
collides CAS 操作collides , 当前bound下CAS失败的次数 , 最大为m , m(bound--tt-darkmode-color: #999999;">collides
几个重要的变量/** * 每个线程状态 */private final Participant participant;/** * 消除阵列; null , 直到启用(在slotExchange内) 。* 元素访问使用易失性get和CAS的仿真 。*/private volatile Node[] arena;/** * 在检测到争用之前一直使用插槽 。*/private volatile Node slot;/** * 每次更新时 , 将最大有效竞技场位置的索引与高位SEQ号进行“或”运算 。* 从0到SEQ的初始更新用于确保舞台阵列仅被构造一次 。*/private volatile int bound;
你看这个 bound 平平无奇 , 实际上还是有写东西需要大家理解一下 。
bound , 记录最大有效的arena索引 , 动态变化 , 竞争激烈时(槽位全满)增加 ,槽位空旷时减小 。
bound + SEQ +/- 1 , 其高位+ 1(SEQ,oxff + 1)确定其版本唯一性(比如 , +1后 , 又-1 , 实际上是两个版本的bound , collides要重置的 , 而且从右向左遍历的索引也要更新 , 一般来讲 , 左边槽位比右边槽位竞争激烈 , 所以要从右向左找 , 为的是快速找到一个空位置 , 并尝试占领它 , 当bound加一又减一后 , 遍历索引右侧的槽位应该就空出来了 , 因为大家都往左边靠拢 , 所以要更新到最右侧 , 如果没有bound的版本唯一性 , 便没有索引更新 , 就一直往左遍历竞争激烈的槽位 , 还会误判 , 本来bound应该缩减的 , 反而又使其增加 , 于是会很影响效率的 。 ) , 低位+/-1实际有效的索引( --tt-darkmode-color: #999999;">bound
构造器我们想使用 Exchanger 类 , 肯定要创建他 。
如何创建呢?
public Exchanger() {participant = new Participant();}
只有一个无参构造器 。
Participant 是什么?
希望你还记得前面的内容 , 这个就是一个 ThreadLocal 的子类简单实现 。
核心方法Exchanger 类最核心的方法其实只有一个:
public V exchange(V x) throws InterruptedException {Object v;Object item = (x == null) ? NULL_ITEM : x; // translate null argsif ((arena != null ||(v = slotExchange(item, false, 0L)) == null)return (v == NULL_ITEM) ? null : (V)v;}
稿源:(未知)
【傻大方】网址:http://www.shadafang.com/c/111T3142H020.html
标题:高并发进阶 Exchanger 双方栅栏源码深度解析( 三 )