LongAdder
LongAdder继承自Striped64,Striped64有一个内部类Cell以及该类的数组,
还又有一个字段叫base,base就是初始值.
Cell
Cell内部有一个字段value,该字段是对base值的增加值,就是当多个线程访问
LongAdder时,会给该线程分配一个Cell,分配方式为
cells[ThreadLocalRandom.current()%cells.length],
然后让该线程的修改用cas操作放在该Cell的中value中,cells的初始长度为2,最
大长度为cpu的核心数.
ThreadLocalRandom.current()是每个线程单独的Random,Random不是线程安全,
该方法会产生一个随机数.
一个cell是会被多个线程访问的,ThreadLocalRandom.current()%cells.length的结果
很可能是相同的,运气不好的话,所有的结果都相等,这个时候LongAdder和AtomicLong
的效率就差不多了.
当然了,该Cell类还加上了@sun.misc.Contended注解,表示每个Cell单独占用
一个缓存行,避免虚共享.
虚共享
虚共享的产生:目前cpu都有三级缓存l1,l2,l3,缓存中的数据存放是一行一行的,
一行是64个字节,long变量的大小是64位也就是8个字节,需要8个long变量才能
填满一个缓存行.
当对一个volatile变量进行操作时,由于缓存一致性协议,要让所
有的线程都对这个变量的修改可见,所有的线程就要将该变量所在的缓存行清空,
结果就导致本来对一个变量的操作,影响了该缓存行存在的其他变量,导致其他变量
也要去内存重新读取,造成不必要的时间开销.
想要避免这个开销,就要拿空间换时间了,一个long是8字节,再填充7个long就行了,
就像这样:
long value;
long p0, p1, p2, p3, p4, p5, p6;
@sun.misc.Contended加上后就相当于这样做了,这样做之后虽然造成了空间的浪
费,但是省去了时间,当然啦,cpu的缓存可是很小的,一个缓存行也是很珍贵的,这就
需要我们自己去衡量了.
基本原理
当只有一个线程访问LongAdder时直接返回base
当只有一个线程修改LongAdder时直接对base进行cas操作
当有多个线程访问LongAdder时返回base + cells的所有value值
当有多个线程修改时LongAdder时,每个线程只对自己对应的cell进行cas操作,
避免了不必要的竞争.