【Java源码计划】LongAdder<rt.jar_java.util.concurrent.atomic.LongAdder>

LongAdder

源码解读

源码解读部分按照我得理解翻译和解读注解并添加相关的部分代码解读

保持一个或者多个变量,初始值设置为零用于求和。当出现多个线程竞争进行一个数的更新时,这个变量集合可以动态的扩展。最后当需要求和的时候或者说需要这个Long型的值时,可以通过把当前这些变量求和,合并后得出最终的和。

这个类在一些多线程环境下表现要比AtomicLong这个类好,比如多个线程同时更新一个求和的变量,比如统计集合的数量,但是不能用作细粒度的同步控制变量,换句话说这个是可能有误差的。在低并发竞争的情况下LongAdder和AtomicLong的性能表现没什么差别,但是当高并发竞争的时候,这个类将具备更好的吞吐性能,但是相应的也会耗费相当的空间。

LongAdder类可以和ConcurrentHashMap一起用一个可扩展的增长序列。例如一个统计一个柱状图或者统计变量的集合之类的功能,如果没有初始化这个key对应value可以通过jdk1.8在map接口中提供的函数 ConcurrentHashMap.computeIfAbsent(k -> new LongAdder().increment());来实现对是否存在的判断和加一的操作。

LongAdder继承了Number抽象类,但是并没有定义一些方法例如,equals,hashCode,compareTo,因为实例的期望用途是进行一些比较频繁的变化,所以自然也就不需要用于作为集合的key。

public LongAdder()

默认构造方法会初始化一个新的adder并将和初始化为0.

public LongAdder() {
    //没什么特殊的
}

public void add(long x)

将给定的值增加进去。

   public void add(long x) {
        //此处涉及的Cell类与Striped64有关
        Cell[] as; 
        long b, v;
        int m;
        Cell a;
        //casBase方法来源于Striped64,放到对应的地方细讲
        //casBase会尝试着进行修改base的操作,如果成功,那就执行结束
        //如果失败意味着发生多线程竞争,会采取措施
        
        if ((as = cells) != null || !casBase(b = base, b + x)) {
            boolean uncontended = true;
            //通过哈希值来获取当前线程对应的cells数组中的位置getProbe()
            //获取该位置上的cell,如果该cell不为null,
            //那么就试图将本次计数累计到该cell上,a.cas()
            //如果不成功,那么就需要Striped64类的longAccumulate方法来进行计数累计
            //涉及的Cell类的方法和Striped64类中的方法会放到对应的地方
            if (as == null || (m = as.length - 1) < 0 ||
                (a = as[getProbe() & m]) == null ||
                !(uncontended = a.cas(v = a.value, v + x)))
                longAccumulate(x, null, uncontended);
        }
    }

以下简要介绍一下Striped64

Striped64是在java8中添加用来支持累加器的并发组件,它可以在并发环境下使用来做某种计数,Striped64的设计思路是在竞争激烈的时候尽量分散竞争,在实现上,Striped64维护了一个base Count和一个Cell数组,计数线程会首先试图更新base变量,如果成功则退出计数,否则会认为当前竞争是很激烈的,那么就会通过Cell数组来分散计数,Striped64根据线程来计算哈希,然后将不同的线程分散到不同的Cell数组的index上,然后这个线程的计数内容就会保存在该Cell的位置上面,基于这种设计,最后的总计数需要结合base以及散落在Cell数组中的计数内容。

类似的设计思想——java7的ConcurrentHashMap实现,也就是所谓的分段锁算法,ConcurrentHashMap会将记录根据key的hashCode来分散到不同的segment上,线程想要操作某个记录只需要锁住这个记录对应着的segment就可以了,而其他segment并不会被锁住,其他线程任然可以去操作其他的segment,这样就显著提高了并发度。

但是注意,虽然这个思想很棒,但是java8中的ConcurrentHashMap实现已经抛弃了java7中分段锁的设计,而采用更为轻量级的CAS来协调并发。

LongAdder就是基于Striped64实现的计数器

Striped64的详细内容我们会放到Striped64的源码中说

public void increment()

方法等价于调用add(1L)方法

    public void increment() {
        //等价于调用了add方法
        add(1L);
    }

public void decrement()

方法等价于调用add(-1L)方法

    public void decrement() {
        //等价于调用了add方法
        add(-1L);
    }

public long sum()

返回当前值,注意返回的数值并不是符合原子性的值快照,在没有并发更新的情况下调用将返回准确的结果,但是在统计计算总和的时候发生的并发更新可能并不会合并进去。

    public long sum() {
        Cell[] as = cells; Cell a;
        //统计base的值和各个cell的值得和
        long sum = base;
        if (as != null) {
            for (int i = 0; i < as.length; ++i) {
                if ((a = as[i]) != null)
                    sum += a.value;
            }
        }
        return sum;
    }

public void reset()

重置所有用于统计总和的变量为0,可以利用这个方法创建一个新的加法器,但是只有在没有并发更新的情况下才有效。因为这个方法本质上是不稳定的,所以只有当知道没有线程同时更新时才可以使用。

public void reset() {
        Cell[] as = cells; Cell a;
        //先把base置为0
        base = 0L;
        //然后将每个cell中的值置为0
        if (as != null) {
            for (int i = 0; i < as.length; ++i) {
                if ((a = as[i]) != null)
                    a.value = 0L;
            }
        }
    }

public long sumThenReset()

方法等价于先求和后进行reset操作,也就是先调用sum然后调用reset。例如,这个方法可以用于求多线程情况下在一个瞬间的稳定计算值。在方法执行期间,如果仍然有线程在进行并发更新,那么返回的值可能是不一致的。(参考源码)

    public long sumThenReset() {
        Cell[] as = cells; Cell a;
        long sum = base;
        base = 0L;
        if (as != null) {
            for (int i = 0; i < as.length; ++i) {
                if ((a = as[i]) != null) {
                    //求和的同事将cell置为零
                    sum += a.value;
                    a.value = 0L;
                }
            }
        }
        return sum;
    }

public String toString()

返回和的值转换为string

    public String toString() {
        return Long.toString(sum());
    }

public long longValue()

等价于调用sum方法。

public long longValue() {
       return sum();
}

public int intValue()

将结果执行强制类型转换为int类型。

   public int intValue() {
       return (int)sum();
   }

public float floatValue()

将结果执行强制类型转换为float类型。

   public float floatValue() {
       return (float)sum();
   }

public double doubleValue()

将结果执行强制类型转换为double类型。

    public double doubleValue() {
       return (double)sum();
   }

源码中接下来是一个静态内部类
private static class SerializationProxy

是一个序列化的代理,用于避免引用序列化中的非公共Striped64

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

推荐阅读更多精彩内容