原子变量操作类
在JUC并发包中有很多原子变量类,比如AtomicInteger,AtomicLong和AtomicBoolean等。他们原理类似,这里讲解AtomicLong类,AtomicLong是原子性递增和递减类,其内部使用Unsafe实现。
1 | public class AtomicLong extends Number implements java.io.Serializable { |
在这里AtomicLong类可以直接获取Unsafe是因为它本身是在rt.jar下面的是通过Bootstrap类加载器加载的。
上面的原子操作类都是使用的CAS非阻塞算法,性能更好,但是在高并发的情况下Atomicxxx还存放着性能问题(会导致线程一直竞争CAS,导致大量资源浪费),在JDK1.8之后提供了高并发下的LongAdder类。
JDK8新增的原子操作类LongAdder
在AtomicLong中,在高并发的情况下大量线程会同时竞争更新同一个原子变量,这样会导致大量线程失败通过无线循环不断进行自旋尝试CAS的操作,这回白白浪费CPU资源。
因为AtomicLong是多个线程竞争同一个原子变量,而LongAdder中则将原子变量和线程一一对应,比如设置一个Cell数组,其中的元素对应着每个线程(通过一定的算法实现),然后最后获取值的时候将base(基础值)和cell数组中的元素值相加。
LongAdder维护了一个延迟初始化的原子性更新数组(默认情况为null)和一个基础值base。由于cell占用的内存相对较大所以是在需要的时候创建,即惰性加载。
因为cell是一个数组,数组中的元素内存地址是连续的,这就很容易导致伪共享的问题,在LongAdder中使用了@Contented注解来避免。
LongAdder代码分析
我们围绕着6个问题去分析源码
LongAdder的结构是怎样的 答:继承了Striped64里面有一个base cell数组和cellBusy自旋操作标志
当前线程应该访问Cell数组里面的哪一个Cell元素 答:获取当前线程的探针(作为每个线程对应哪一个cell的算法基础),根据当前线程的随机数ThreadLocalRandomProbe和cell元素个数计算当前要访问的cell元素的下标,如果发现对应下标的元素为空则新增一个Cell元素到数组中并在之前将cellBusy设置为1防止其他线程竞争
如何初始化Cell数组 答:懒加载 但需要操作的时候进行初始化操作
Cell数组如何进行扩容 答:当CPU个数大于cell元素进行扩容 这时会多个CPU(线程)争抢一个cell元素产生冲突
线程访问分配的Cell元素后有冲突后应该如何处理 答:进行扩容操作
如何保证线程操作被分配的Cell元素的原子性 答:使用volatile保证内存可见性,使用cas操作保证操作原子性

我们可以看到LongAdder继承Striped64,而Striped64中维持着base,cellBusy,cell三个变量。
base是用来计算LongAdder的真实值的(base和cell元素相加),cellBusy是用来实现自旋锁的,状态值只有0和1,当创建Cell元素,扩容Cell数组或者初始化Cell数组的时候使用CAS操作该变量来保证同时只有一个线程可以进行其中之一的操作。
我们来看一下Cell的构造
1 | .misc.Contended static final class Cell { |
我们来看一下LongAdder源码
1 | // 继承了Striped64 |
我们来看一下longAccumulate函数源码
1 | final void longAccumulate(long x, LongBinaryOperator fn, |
我们继续回顾一下刚刚上面的longAccumulate方法源码中最后一个else if
1 | // 很明显 当fn为null的时候使用了v + x操作 不然调用该fn的applyAsLong |