Striped64

273 阅读9分钟

一个局部包的类,具有支持在64位值上动态条带化的类的通用表示形式和机制。这个类继承于Number,所以具体的子类必须公开这么做。

abstract class Striped64 extends Number{
    
}

这个类维护着一个懒加载的原子更新值的表,加上一个额外的“base”属性。表大小是2的幂次方。索引使用每个线程哈希值的掩码。在这个类中,几乎所有的申明都是包私有的,直接被子类访问。

表的条目是类Cell,一个填充的原子长整型的变种,以用于减少缓冲冲突。对于大部分原子类来说,填充是滥用的,因为他们在内存中一般是不规则分散的,因此彼此之间互不干扰。但是驻留在数组中的原子对象将倾向于彼此相邻放置,所以不稍加注意的话经常会共享缓冲行(对性能产生巨大的负面影响)。

一般因为Cells相对较大,我们避免在需要它们之前创建。当没有冲突的时候,所有的更新都作用于base属性。直到第一次冲突(在更新base时的失败CAS操作),这个表被初始化成大小为2。再次碰撞的时候这个表大小以2倍扩容,直到大于或者等于cpu数量的最小的2的幂次方。表槽在需要之前保持为空。

单个spinlock(“cellsBusy”)用于初始化表和重新计算表大小,也使用在新Cell填充槽。不需要blocking lock,当lock不可用的时候,线程会尝试其他slots(或者base值)。重试的时候,争夺增加,位置减少,但仍然比替代方法好。

通过ThreadLocalRandom维护的Thread探针字段,用作每个线程的hash code.直到他们在0槽冲突之前,我们让他们一直保持0值未初始化状态(如果他们以这种方式来的话)。然后他们被初始化成与其他值很少冲突的值。执行更新操作时,失败的cas操作表示了争夺 和或者表冲突。发生冲突时,如果表大小小于容量,除非其他线程持有这个lock,否则表大小将增加一倍。如果一个hashed slot是空的,lock可用,则会创建一个新的Cell。否则,如果这个槽存在,会尝试CAS。尝试通过双重哈希进行,使用一个辅助哈希(马萨格力异或移位)去尝试查找到空闲插槽。

该表的大小是有上限的,因为当线数大于cpu时,假设每个线程都绑定到一个cpu,那就存在一个完美的哈希函数,将线程映射到插槽中可以消除冲突。当我们达到容量大小时,我们通过随机更改冲突线程的哈希值去寻找映射。因为搜索是随机的,并且仅通过cas失败才直到冲突,因此收敛会很慢,因为线程通常不会永远绑定在cpu上,因此可能根本不发生。但是,尽管有这些限制,在这些情况下,观察到的竞争率通常会很低。

当曾经对其hash的线程终止时,以及在将表加倍导致没有线程在扩展掩码下对它进行hash处理的情况下,Cell可能变得无用。 在长时间运行的情况中,我们不会检测或者删除此类Cell,因为观察的争用级别会再现,所以会再次需要这些cells。对于短时间的情况,无关紧要。

插槽中值的类型Cell

AtomicLong的填充变体,仅支持原生访问以及CAS。 JVM内部提示:如果提供的话,它可以使用仅发行模式的CAS。

@sum.misc.Contended static final class Cell{
    volatile long value;
    Cell(long x){value = x;}
    final boolean cas(long cmp,long val){
        return UNSAFE.compareAndSwapLong(this,valueOffset,cmp,value);
    }
    
    private static final sun.misc.Unsafe USNAFE;
    private static final long valueOffset;
    
    static{
        try{
            UNSAFE=sum.misc.Unsafe.getUnsafe();
            Class<?> ak=Cell.class;
            valueOffset = UNSAFE.objectFieldOffset(ak.getDeclaredField("value"));
        }catch(Exception e){
            throw new Error(e);
        }
    }
}

Striped64的属性值

/** CPU的数量,用于设置表大小的上限 */
static final int NCPU=Runtime.getRuntime().availableProcessor();

/** 元素表.当不为空时,大小是2的幂次方.*/
transient volatile Cell[] cells;

/** 基础值,大部分用于当没有冲突的时候,但是也作为表初始化竞争时候的后备。通过CAS更新。 */
transient volatile long base;

/** 自旋锁(通过CAS锁住)用于重新计算表大小 和/或 创建元素。*/
transient volatile int cellsBusy;

Striped64(){
}

//Unsafe机制获取的值
private static final sun.misc.Unsafe UNSAFE;
//base值在类中的偏移地址
private static final long BASE;
//自旋锁在类中的偏移地址
private static final long CELLSBUSY;
//线程探针值在Thread中的偏移地址,用于散列值进而获取在表table的索引值
private static final long PROBE;

static{
    try{
        UNSAFE = sun.misc.Unsafe.getUnsafe();
        Class<?> sk = Striped64.class;
        BASE = UNSAFE.objectFieldOffset
            (sk.getDeclaredField("base"));
        CELLSBUSY = UNSAFE.objectFieldOffset
            (sk.getDeclaredField("cellsBusy"));
        Class<?> tk=Thread.class;
        PROBE = UNSAFE.objectFieldOffset
            (tk.getDelcaredField("threadLocalRandomProbe"));
    }catch (Exception e){
        throw new Error(e);
    }
}

方法:

/** base属性值的CAS操作.*/
final boolean casBase(long cmp,long val){
    return UNSAFE.compareAndSwapLong(this,BASE,cmp,val);
}

/** 自旋锁cellsBusy属性从0到1区获取锁的CAS操作 */
final boolean casCellsBusy(){
    return UNSAFE.compareAndSwapInt(this,CELLSBUSY,0,1);
}

/** 获取当前线程的探针值.由于包名限制,从ThreadLocalRandom中复制出来的。(使用UNSAFE操作的)*/
static final int getProbe(){
    return UNSAFE.getInt(Thread.currentThread(),PROBE);
}

/** 伪随机地前进并记录给定线程的给定探针值.由于包名限制,从ThreadLocalRandom中复制出来的.*/
static final int advanceProbe(int probe){
    probe ^= probe <<13;
    probe ^= probe >>>17;
    probe ^= probe << 5;
    UNSAFE.putInt(Thread.currentThread(), PROBE,probe);
    return probe;
}

涉及表的初始化,重新计算表的大小(扩容),创建新单元格,以及冲突的处理情况更新。请查看开头的解释。这个方法依赖于重新检查的读取集,遭受了乐观重试代码常见的非模块化的问题。

/* x  叠加值
   fn 更新函数,如果为空使用加法(这个惯例使得LongAddr避免了添加额外的属性火法函数的需要).
   wasUncontended false表示在子类中调用这个方法之前已经CAS失败了 */
   
final void longAccumulate(long x, LongBinaryOperator fn,boolean wasUncontended){
    //当前线程的hash值,用于获取在表中的索引值
    int h;
    if((h = getProbe()) == 0){
        //说明没有初始化过,让线程强制初始化这个线程局部变量值
        ThreadLocalRandom.current();
        h = getProbe();
        wasUncontended = true;
    }
    boolean collide = false; //true表示插槽不为空
    
    for(;;){
        Cell[] tempCells;
        Cell operateCell;
        int cellLength;
        long value;
        if(tempCells = cells)!=null&&(cellLength=tempCells.length)>0{
            //表不为空
            if((operateCell = tempCells[(cellLength-1)&h]) == null){
                //这个插槽的值为空,没有初始化过
                if(cellsBusy == 0){
                    //自旋锁空闲
                    Cell r=new Cell(x);//乐观创建新单元格
                    if(cellsBusy == 0 && casCellsBusy()){
                        //尝试获取自旋锁,也就是将自旋锁的值从0变成1
                        boolean created = false;
                        try{
                            Cell[]resultCells;
                            int m,j;
                            //再次判断是否为空,防止上个线程在本线程获取这个自旋锁之前已经初始化这个插槽值了
                            if((resultCells = cells)!=null && (m=resultCells.length)>0 && resultCells[j= (m-1)&h] == null){
                                resultCells[j] = r;
                                created =  true;
                            }
                        }finally{
                            //释放自旋锁
                            cellsBusy=0;
                        }
                        if (created){
                            //成功初始化插槽
                            break;
                        }
                        //上个线程已经初始化了,插槽已经不为空了,退出重新循环
                        continue;
                    }
                }
                collide = false;
            }
            //插槽值不为空
            else if(!wasUncontended){
                //CAS已知失败。所以重新hash之后继续循环获取新的插槽。
                wasUncontended =  true;
            }else if(operateCell.cas(value = operateCell.value,((fn == null)? value+x:fn.applyAsLong(value,x)))){
                //特定插槽单元格上cas操作,成功就退出循环
                break;
            }else if(n>=NCPU || cells != tempCells){
                //表容量大于cpu核数,或者当前表数组已经被其他线程扩容过了,就将冲突值collide设为 false
                collide=false;
            }else if(!collide){
                //冲突值为false,重新hash重新循环
                collide=true;
            }else if(cellsBusy == 0 && casCellsBusy()){
                //扩容
                try{
                    if(cells == tempCells){
                        //除非过时(已经被扩容过了),否则扩容一倍
                        Cell[] rs = new Cell[n<<1];
                        for(int i = 0; i<n; ++i)
                            rs[i] = tempCells[i];
                        cells = rs;
                    }
                }finally{
                    cellsBusy = 0;
                }
                collide = false;
                continue;
            }
            //重新hash,获取新的插槽位置去更新
            h = advanceProbe(h);
        }
        //表没有初始化过
        else if(cellsBusy == 0 && cells == tempCells && casCellsBusy()){
            boolean init = false;
            try{
                if(cells == tempCells){
                    Cell[] rs = new Cell[2];
                    rs[h&1] = new Cell(x);
                    cells = rs;
                    init = true;
                }
            }finally{
                cellsBusy = 0;
            }
            if(init)
                break;
        }
        //有线程正在初始化表,所以最后再尝试更新一下基础值base
        else if(casBase(value = base,((fn == null)? value + x: fn.applyAsLong(value,x))))
            break;
    }
}

doubleAccumulate:和longAccumulate相同,但由于此类的开销较低,因此在太多的地方注入长整型/双浮点型转换以明智的与长整型合并。所以必须通过 复制/粘贴/调整来维护。将double值转成long表示的double,操作的时候转成double进行操作,最后再转成long保存.

final void doubleAccumulate(double x, DoubleBinaryOperator fn,boolean wasUncontended){
    int h;
    if((h = getProbe()) == 0){
        ThreadLocalRandom.current();
        h = getProbe();
        wasUncontended = true;
    }
    boolean collide = false;
    for(;;){
        Cell[] tempCells;
        Cell opeateCell;
        int length;
        long value;
        
        if((tempCells = cells)!=null && (length=tempCells.length)>0){
            //数组不为空
            if((operateCell = tempCells[(length-1) & h]) == null){
                //单元格插槽数据为空
                if(cellsBusy == 0){
                    //准备初始化这个插槽的值
                    Cell result=new Cell(Double.doubleToRawLongBits(x));
                    if(cellsBusy == 0 && casCellsBusy()){
                        boolean created = false;
                        try{
                            Cell[] resultCells;
                            int m,j;
                            if((resultCells = cells)!=null&&(m = resultCells.length)>0 &&
                            result[j = (m-1) & h] == null){
                                //确定这个插槽没有被初始化
                                resultCells[j] = r;
                                created = true;
                            }
                        }finally{
                            cellsBusy = 0;
                        }
                        if(created)
                            break;
                            
                        continue;
                    }
                }
                collide = false;
            }
            //单元格插槽数据不为空
            else if(!wasUncontended){
                //cas失败过
                wasUncontended=true;
            }
            else if(operateCell.cas(v = operate.value,(fn == null)?Double.doubleToRawLongBits(Double.longBitsToDouble(v)+x):Double.doubleToRawLongBits(fn.applyAsDouble(Double.longBitsToDouble(v),x)))))
            break;
            else if(n >= NCPU ||  cells != tempCells){
                //数组长度大等于cpu核数,或者到这里数组被其他线程更改过了,表示有冲突或者拦截线程扩容
                collide = false;
            }
            else if(!collide){
                //因为数组长度到顶了,或者因为插槽初始化失败,或者扩容
                collide = true;
            }
            else if(cellsBusy == 0 && casCellsBusy()){
                //扩容
                try{
                    if(cells == tempCells){
                        Cell[] resultCells = new Cell[n << 1];
                        for(int i=0;i<n;++i){
                            resultCells[i]=tempCells[i];
                        }
                        cells = resultCells;
                    }
                }finally{
                    cellsBusy = 0;
                }
                collide = false;
                continue;
            }
            //二次哈希,重新寻找新的下标
            h = advanceProbe(h);
        }
        //数组为空,初始化数组,长度为2
        else if(cellsBusy ==0 && cells == tempCells &&casCellsBusy()){
            //获取自旋锁
            boolean init = false;
            try{
                if(cells == tempCells){
                    Cell[]resultCells=new Cell[2];
                    resultCells[h&1]=new Cell(Double.doubleToRawLongBits(x));
                    cells = rs;
                    init = true;
                }
            }finally{
                cellsBusy = 0;
            }
            if(init)
                break;
        }
        //数组为空,但是有线程在初始化数组,所以在base值上尝试cas操作
        else if(casBase(value = base,((fn==null)?
        Double.doubleToRawLongBits(Double.longBitsToDouble(value)+x):
        Double.doubleToRawLongBits(
            fn.applyAsDouble(Double.longBitsToDouble(value),x))))
            break;
    }
}

Striped64用于LongAccumulator/LongAdder/DoubleAccumulator/DoubleAddr:其中的function:LongBinaryOperator或者DoubleBinaryOperator只能使用累加顺序无关的函数,否则最终的值是不确定的。