• 简单看看LongAccumulator


      上篇博客我们看了AtomicLong和LongAdder的由来,但是有的时候我们想一下,LongAdder这个类也有点局限性,因为只能是每一次都+1,那有没有办法每次+2呢?或者每次乘以2?说得更抽象一点,我们能不能自己指定规则呢?干嘛老是傻乎乎的+1呢?

      于是就有了LongAccumulator这个累加器,这个累加器更加抽象,前面使用的LongAdder只不过是这个累加器的一个特例,由此我们可以猜出这个累加器功能更加强大,但是需要我们自己的定制规则;

      前提:看本篇博客的人应该熟悉jdk8中的函数式编程,jdk8是在2014年3月18日就推出了,到现在已经将近6年了,但是我们还是很多人在用着jdk8写着jdk7版本的代码,哎,无力吐槽!既然不能改变别人就改变自己吧!

    一. 简单使用LongAccumulator累加器

      我们先看看这个累加器的构成,如下所示:

    public class LongAccumulator extends Striped64 implements Serializable {
        //这是一个函数式接口,函数描述符是(T,T)->T ,两个相同类型的数据按照某种规则运算,返回相同类型的数据
        private final LongBinaryOperator function;
        //这个是累加器的初始值,也就是相当于LongAdder的base字段,就好像LongAdder的初始值是0一样,但是这里的累加器初始值可以不为0
        private final long identity;
    
        //可以看到构造函数中要传进去一个函数行为,还有初始值
        public LongAccumulator(LongBinaryOperator accumulatorFunction, long identity) {
            this.function = accumulatorFunction;
            base = this.identity = identity;
        }
    
    //这个就是函数式接口,用这个@FunctionalInterface注解标识,函数描述符是(T,T)->T
    @FunctionalInterface
    public interface LongBinaryOperator {
        long applyAsLong(long left, long right);
    }

      下面我们就简单的使用了,看看LongAccumulator在实例化的时候,我们使用Lambda表达式指定了累加规则,其实就是将传进去的数据和初始值进行累加,而传进去的数字可以在accumulate方法里面动态指定!

    package com.example.demo.study;
    
    import java.util.concurrent.atomic.LongAccumulator;
    
    public class Study0127 {
    
        //这里使用LongAccumulator类
        public LongAccumulator num = new LongAccumulator((a,b)->a+b, 0);
        
    
        //每次调用这个方法,都会对全局变量加一操作,执行10000次
        public void sum() {
            for (int i = 0; i < 10000; i++) {
                //LongAccumulator类的自增操作,这里的参数可以指定每次增加的数字
                num.accumulate(1);
                System.out.println("当前num的值为num= "+ num);
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            Study0127 demo = new Study0127();
            //下面就是新建两个线程,分别调用一次sum方法
            new Thread(new Runnable() {
                @Override
                public void run() {
                    demo.sum();
                }
            }).start();
    
            new Thread(new Runnable() {
    
                @Override
                public void run() {
                    demo.sum();
                }
            }).start();    
        }
    }

      如果我们每个线程都要对一个初始值乘以2,那么可以像下面这样修改,只需要修改lambda表达式,初始值还有线程每进行一次运算所需要的数字就行了,有兴趣的可以自己试试

    ....省略部分代码
    //注意这里的lambda表达式
    public LongAccumulator num = new LongAccumulator((a,b)->a*b, 1);
        
    //每次调用这个方法,都会对全局变量乘以2,每个线程都乘以4次
    public void sum() {
        for (int i = 1; i < 5; i++) {
         //LongAccumulator类指定一个数字
          num.accumulate(2);
          System.out.println("当前num的值为num= "+ num);
        }
    }
    .....省略部分代码

    二.走进LongAccumulator

      其实没什么特别想看的吧,就是很多的两个if语句那里一堆判断,注意这里的base是我们实例化LongAccumulator累加器时候传进去的初始值

      第一个if语句,会尝试更新将初始值和每一步要计算的值运算返回结果,更新初始值,如果初始值更新完毕之后就不会往下走;如果初始值更新失败,那么往下走

      第二个if语句,只有当找到了对应的Cell数组中的Cell元素用CAS更新值的时候失败才会进入到if里面

      用上面的乘以2的例子说一下,一个线程,初始值base是1,乘以2运算,结果是2,此时使用CAS尝试将初始值base设置为2,如果成功那么当前线程执行完毕;设置失败(可能是其他线程已经更新了base的值了),那么就去找Cell数组中的某个Cell元素去更新它,更新成功,则线程执行完毕;更新失败,去初始化Cell数组、扩容Cell数组等操作了

     public void accumulate(long x) {
        Cell[] as; long b, v, r; int m; Cell a;
        //注意这里这个if语句的第二个判断条件,这里就是传进去function.applyAsLong(b = base, x)),会调用我们传进去的lambda行为计算
        if ((as = cells) != null ||  (r = function.applyAsLong(b = base, x)) != b && !casBase(b, r)) {
            boolean uncontended = true;
            if (as == null || (m = as.length - 1) < 0 || (a = as[getProbe() & m]) == null || 
                 !(uncontended = (r = function.applyAsLong(v = a.value, x)) == v || a.cas(v, r)))
                 //下面这个方法上次说了,就是对Cell数组的初始化,扩容和新创建Cell的操作,只不过在LongAdder中传递进去的function是null,
                 //而这里传进去的就是我们自己定义的lambda行为,就不进去看了,基本一样
                longAccumulate(x, function, uncontended);
        }
    }
    //用CAS更新base的值
     final boolean casBase(long cmp, long val) {
        return UNSAFE.compareAndSwapLong(this, BASE, cmp, val);
    }

      

      这个longAccumulate()方法上一篇已经分析过了,就不多说了,唯一不同的就是这次的fn不为null,所以这里不是v+x,而是fn.applyAsLong(v, x),也就是我们传进去的lambda表达式

      这次这个类没什么新的东西吧,就是用到了jdk8的行为参数化,我们传进去的不再是一个数值或者字符串,更应该说是传进去一个函数,熟悉js的人应该知道,有兴趣的可以了解一下!

  • 相关阅读:
    PKU JudgeOnline 题目分类
    调试时拼凑带端口的完整网址/域名
    智能电脑监控器,完美解决想监控别人在自己电脑上的一切操作。
    如何清理LDF文件
    使用母版页后FindConttol需要注意
    【外刊IT评论】代码覆盖率:80%,不能少
    推荐2本普通人参悟的书
    处理在母版页加AJAX环境下处理滚动条回发保持不动的问题
    虚拟目录中的web.config不被上级目录的web.config影响的处理
    C++中^符号的意思
  • 原文地址:https://www.cnblogs.com/wyq1995/p/12242984.html
Copyright © 2020-2023  润新知