- 原子累加器(JDK1.8):AtomicLong和AtomicDouble的升级类型,专门用于数据统计,性能更高。
- DoubleAccumulator
- DoubleAdder
- LongAccumulator
- LongAdder
- 伪共享问题
- 缓存行失效 / 伪共享
- @sun.misc.Contended注解
- Unsafe对象
7.1 原子累加器
(1)LongAdder,DoubleAdder
- 作用:虽然AtomicLong等原子类能够通过CAS进行原子累加操作,但在多线程竞争激烈的情况下很容易CAS失败,导致性能不高。原子累加器就是改进方法
- 原理:底层有一个Base变量和Cell[]数组变量,加的时候首先尝试用CAS对base进行加,如果失败,那么转而对cell进行累加。这样同时争取一个变量的线程就变少了,而是分散成对多个变量的竞争,减少了失败次数。如果竞争某个Cell变量失败,它不会一直在这个Cell变量上自旋CAS重试,而是尝试在其他的Cell变量上进行CAS尝试,这个改变增加了当前线程重试CAS成功的可能性。最后,在获取LongAdder当前值时,是把所有Cell变量的value值累加后再加上base返回的
(2)LongAccumulator,DoubleAccumulator
- 作用:LongAccumulator相比于LongAdder,可以为累加器提供非0的初始值,后者只能提供默认的0值。另外,前者还可以指定累加规则,比如不进行累加而进行相乘,只需要在构造LongAccumulator时传入自定义的双目运算器即可,后者则内置累加的规则
7.2 LongAdder / DoubleAdder
这里以LongAdder为例
(1)流程图
- 由于cells数组懒加载,先判断cells数据是否为null——>如果为null说明线程竞争不激烈,CAS对base累加,累加失败再创建cells;如果不为null则对cells数组进行操作
(2)类图
- Cell类是Striped64类中的静态内部类,LongAdder继承了Striped
(3)源码
-------------------------------Striped64类-------------------------------
//累加单元数组,惰性初始化
transient volatile Cell[] cells;
//如果没有竞争,则用cas累加这个base
transient volatile long base;
//在cells创建或扩容时,置为0,表示加锁;初始为0
transient volatile int cellsBusy;
//Striped64的静态内部类Cell
@sun.misc.Contended //防止缓存行伪共享
static final class Cell {
volatile long value;
Cell(long x) { value = x; }
//最重要的方法,用CAS进行累加,pre表示旧值,next表示新值
final boolean cas(long cmp, long val) {
return UNSAFE.compareAndSwapLong(this, valueOffset, pre, next);
}
}
(4)API
-
----------------------------------LongAdder---------------------------------- //1. 这是计算总和的,即将所有Cell里面的元素和base一起加起来。由于计算时没有对Cell数组加锁,所以累加过程中其他线程可能修改Cell的值或对数组扩容,所以sum返回的值不精确,并不是一个原子快照 long sum() //2. 把base置0,并把Cell数组内有元素的都重置为0 void reset() //3. 就是sum()和reset()合起来,求和的过程中重置。由于也没有加锁,该方法在多线程环境下会有问题,比如一个线程执行了这个方法,而另一个线程紧随着也执行这个方法则总会返回0 long sumThenReset() //4. 等价于sum() longValue() //5. 这个方法是加一个数,看一下它是如何执行的 add()
7.3 LongAccumulator
7.4 伪共享问题
- 什么是伪共享?什么是缓存失效?
- Thread1和Thread2都需要对Cell[]数组作修改,Thread1修改Cell[0],Thread2修改Cell[1]
- 但由于Cell[0]和Cell[1]两个共享变量在同一个缓存行中(Cell数组元素的内存地址是连续的,所以数组内的多个元素能经常共享缓存行)
- 如果Thread1修改Cell[0],会导致Thread1的缓存行失效(MESI协议中,缓存行会有个status,通过status可以判断自己失效了),Thread1需要重新刷新缓存
- 这样一来,Thread1和Thread2修改Cell[]时,每次会让对方刷新缓存,缓存的作用就体现不出来了(本来指望通过缓存加载更快)
- 这就是伪共享问题,看上去通过缓存共享了数据,实际相互一直让对方的缓存失效
- 注:最常见的缓存行大小是64个字节
- Java的解决方案
- 传统方案:VolatileLong类型,原理是自动补齐(Padding)至一整个缓存行的大小
- Java8的方案:Java8中新增了一个注解:@sun.misc.Contended。加上这个注解的类会自动补齐缓存行,需要注意的是此注解默认是无效的,需要在jvm启动时设置-XX:-RestrictContended才会生效
- Reference:https://blog.csdn.net/hanmindaxiongdi/article/details/81159314
7.5 Unsafe对象
- 包路径:sun.misc.Unsafe
- 介绍:都是本地native方法,不要轻易使用(从JDK9开始,Java的新模块化设计将使得非标准库的模块都无法访问到sun.misc.Unsafe)
- 常用的native方法
- synchronized相关
- public native void monitorEnter(Object var1);
- public native void monitorExit(Object var1);
- 异常
- public native void throwException(Throwable var1);
- CAS相关
- public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
- public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
- public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
- 线程
- public native void park(boolean var1, long var2);
- public native void unpark(Object var1);
- synchronized相关
- 使用Unsafe对象的方法
- 暴力反射