LongAccumulator 和 DoubleAccumulator 的参数 DoubleBinaryOperator 和 LongBinaryOperator

关键字:Accumulator 没能获得预期值,BinaryOperator 表达式如何写,Accumulator 出错

以下都以 Double 类型的说明

基本使用

LongAccumulator 和 DoubleAccumulator 是 Java 提供的用于为多线程提供原子值的一个类,可以视作一个能保证线程安全的 long 类型和 double 值来用

        DoubleBinaryOperator doubleBinaryOperator = (a, b) -> {            
            return a * b;
        };
        // 创建一个 DoubleAccumulator 并设置初始值
        DoubleAccumulator doubleAccumulator = new DoubleAccumulator(doubleBinaryOperator,2f);

        // 根据 上面提供的 operator 运算,这里也就是  doubleBinaryOperator提供的值=2*3 变为6
        doubleAccumulator.accumulate(3);
        // 返回 doubleAccumlator 提供的值
        int aaaaaa=doubleAccumulator.get();
参数 DoubleBinaryOperator

在 Java核心技术,关于LongAccumulator 和 DoubleAccumulator 的介绍中,

DoubleAccumulator 的实现方式,简单的说就是在内部提供了一组变量

identity ,a1, a2 ,a3 ···

调用 doubleAccumulator.accumulate(x)

  • 线程不冲突的时候 , 直接 identity = doubleBinaryOperator (identity ,x)

  • 线程冲突的时候,根据线程选择一个变量 a1= doubleBinaryOperator (a1,x),若 a1 没有被使用过,直接把 x 赋值给 a1 作为 a1 的初始值

在需要doubleAccumulator 提供值得时候也就是 调用 doubleAccumulator.get(); 的时候

提供 identity  op  a1 op a2 op a3 ...  
即
result = identity;
result= doubleBinaryOperator  (result,a1);
result= doubleBinaryOperator  (result,a2);
result= doubleBinaryOperator  (result,a3);
····

return  result;

根据 以上的逻辑,那么 DoubleBinaryOperator 的实现的运算规则,是收到一定限制的

如果 运算规则 是 a*b 

未产生线程冲突下
doubleAccumulator.accumulate(p1);
doubleAccumulator.accumulate(p2);

identity = identity*p1;
identity = identity*p2;

计算 result 的时候相当于
result =identity *p1 *p2;

线程冲突,使用 a1 
a1=p1;
a1=p1*p2;
计算 result 的时候相当于
result=identity* (p1*p2);
------------------------------------------------------------
如果 运算规则 是 a-b 

未产生线程冲突下
doubleAccumulator.accumulate(p1);
doubleAccumulator.accumulate(p2);

identity = identity-p1;
identity = identity-p2;

计算 result 的时候相当于
result =identity -p1 -p2;

线程冲突,使用 a1 // 这个 a1 其实就是源码中的 cell
a1=p1;
a1=p1-p2;
计算 result 的时候相当于
result=identity- (p1-p2);

result值为 identity-p1+p2

并不会获得我们期望的数值
--------------------------------------
所以一定要考虑好如何写 运算规则
源码

DoubleAccumulator.class



     public DoubleAccumulator(DoubleBinaryOperator accumulatorFunction,
                             double identity) {
        this.function = accumulatorFunction;
         // 构造方法,在这里可以看到 初始值得赋予
        base = this.identity = Double.doubleToRawLongBits(identity);
    }


     /**
     * Updates with the given value.
     *
     * @param x the value
     */
    public void accumulate(double x) {
        Cell[] as; long b, v, r; int m; Cell a;
        if ((as = cells) != null ||
            (r = Double.doubleToRawLongBits
             (function.applyAsDouble
              (Double.longBitsToDouble(b = base), x))) != b  && !casBase(b, r)) {
            // as 非空的时候直接查数组,看有没有线程对应的 cell 
            boolean uncontended = true;
            if (as == null || (m = as.length - 1) < 0 ||
                (a = as[getProbe() & m]) == null ||
                !(uncontended =
                  (r = Double.doubleToRawLongBits
                   (function.applyAsDouble
                    (Double.longBitsToDouble(v = a.value), x))) == v ||
                  a.cas(v, r)))
                // 找不到的时候就进入此方法 为线程构建一个它对应的 cell,此方法实现见下端
                doubleAccumulate(x, function, uncontended);
        }
    }


    /**
     * Returns the current value.  The returned value is <em>NOT</em>
     * an atomic snapshot; invocation in the absence of concurrent
     * updates returns an accurate result, but concurrent updates that
     * occur while the value is being calculated might not be
     * incorporated.
     *
     * @return the current value
     */
    public double get() {
        Cell[] as = cells; Cell a;
        double result = Double.longBitsToDouble(base);
        if (as != null) {
            for (int i = 0; i < as.length; ++i) {
                if ((a = as[i]) != null)
                    // 把 as 遍历,挨个做 applyAsDouble ,就是自己写的 运算规则
                    result = function.applyAsDouble
                        (result, Double.longBitsToDouble(a.value));
            }
        }
        return result;
    }


    final void doubleAccumulate(double x, DoubleBinaryOperator fn,boolean wasUncontended) {
        // 省略的其他代码
        // 在这里可以看到,新建 Cell 的时候,就是直接 把 x 赋做初值 ,对应上面 a1=p1;
        Cell r = new Cell(Double.doubleToRawLongBits(x));
}

测试

测试思路:

两组使用相同的初始值 ,和运算法则

组 1 ,使用 DoubleAccumulator,用两个线程 ,总共循环计算 2*times 次

组 2 ,使用 普通 double 类型,单线程 ,循环 计算 2*times 次,这个计算结果一定是我们期望的

调整 times 的数值,当 times 较小的时候,不容易产生同步问题,就是一直在使用 identity 做计算,可以看到 最后两组计算结果相同

当 times 较大时,线程冲突,使用了其他变量,可以看到 最后两组计算结果不相同

        // 初始值
        Double x=2.0d;

        // 每个线程循环次数
        int times=500000;
        // 赋一个差值。让两个线程执行不同次数
        int chazhi=-490000;
        // 运算方式
        DoubleBinaryOperator doubleBinaryOperator = (a, b) -> {
            //return (a*b>999999||a*b<0.0000011)?a:a*b;
            return a - b;
        };
        DoubleAccumulator doubleAccumulator = new DoubleAccumulator(doubleBinaryOperator,2.0d);



        new Thread(()->{
            for (int i = 0;i<times+chazhi; i++) {

//                if (Double.isNaN(doubleAccumulator.get())||Double.isInfinite(doubleAccumulator.get())){
//                    System.out.println("t1 停止了");
//                    break;
//                }

//                if (doubleAccumulator.get()>99999f){
//                    System.out.println(doubleAccumulator.get()+"------t1 跳过");
//                    continue;
//                }

                doubleAccumulator.accumulate(x);
                //System.out.println("t1第"+i+"次:"+doubleAccumulator.get());
            }
            System.out.println("t1 结束循环了 "+doubleAccumulator.get());
        }).start();

        new Thread(()->{
            for (int i = 0;i<times-chazhi; i++) {
//                if (Double.isNaN(doubleAccumulator.get())||Double.isInfinite(doubleAccumulator.get())){
//
//                    System.out.println("t2 停止了");
//                    break;
//                }

//                if (doubleAccumulator.get()<0.0001f){
//                    System.out.println(doubleAccumulator.get()+"------t2 跳过");
//                    continue;
//                }

                doubleAccumulator.accumulate(x);
                //System.out.println("t2第"+i+"次:"+doubleAccumulator.get());
            }
            System.out.println("t2 结束循环了 "+doubleAccumulator.get());
        }).start();



        Double a= 2.0d;
        for (int i = 0; i < times*2; i++) {
            a=doubleBinaryOperator.applyAsDouble(a,x);
        }

        Thread.sleep(3000);
        System.out.println("a的值----------- "+ BigDecimal.valueOf(a));
        System.out.println("l的值----------- "+BigDecimal.valueOf(doubleAccumulator.get()));

运算规则为 a+b 的时候,无论如何 a , l 值相同

运算规则为 a-b 的时候

尝试在 doubleAccumulate(x, function, uncontended); 这个位置打断点

就会发现,只要在此位置触发了断点, a 和 l 的值就出现不同,因为使用了 cell ,这种运算规则在这种逻辑下不能满足我们的预期,最后计算结果的时候就出错了

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,874评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,102评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,676评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,911评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,937评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,935评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,860评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,660评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,113评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,363评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,506评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,238评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,861评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,486评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,674评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,513评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,426评论 2 352