原子变量操作类AtomicLong
JUC并发包中包含有Atomiclnteger、AtomicLong和AtomicBoolean等原子性操作类,它们的原理类似,本章讲解AtomicLong类。AtomicLong是原子性递增或者递减类,其内部使用Unsafe来实现。
AtomicLong类就是通过BootStarp类加载器进行加载的,故可以通过Unsafe.getUnsafe()方法获取到Unsafe类的实例。
递增和递减操作代码
都是通过调用Unsafe的getAndAddLong方法来实现操作,这个函数是个原子性操作,这里第一个参数是AtomicLong实例的引用,第二个参数是value变量在AtomicLong中的偏移值,第三个参数是要设置的第二个变量的值。
booleancompareAndSet(longexpect,longupdate)方法
内部还是调用了unsafe.compareAndSwapLong方法。如果原子变量中的value值等于expect,则使用update值更新该值并返回true,否则返回false。
JDK8新增的原子操作类LongAdder
使用AtomicLong时,在高并发下大量线程会同时去竞争更新同一个原子变量,但是由于同时只有一个线程的CAS操作会成功,这就造成了大量线程竞争失败后,会通过无限循环不断进行自旋尝试CAS的操作,而这会白白浪费CPU资源。
所以LongAdder,是在内部维护多个Cell变量,每个Cell里面有一个初始值为0的long型变量,这样,在同等并发量的情况下,争夺单个变量更新操作的线程量会减少,这变相地减少了争夺共享资源的并发量。另外,多个线程在争夺同一个Cell原子变量时如果失败了,它并不是在当前Cell变量上一直自旋CAS重试,而是尝试在其他Cell的变量上进行CAS尝试,这个改变增加了当前线程重试CAS成功的可能性。最后,在获取LongAdder当前值时,是把所有Cell变量的value值累加后再加上base返回的。
LongAdder维护了一个延迟初始化的原子性更新数组(默认情况下Cell数组是null)和一个基值变量base。由于Cells占用的内存是相对比较大的,所以一开始并不创建它,而是在需要时创建,也就是惰性加载。
使用@sun.misc.Contended注解对Cell类进行字节填充,这防止了数组中多个元素共享一个缓存行。
LongAdder代码分析
(1)LongAdder的结构是怎样的?
public class LongAdder extends Striped64 implements Serializable
LongAdder 类继承自 Striped64 类,在 Striped64 内部维护着三个变量。 LongAdder 的真实值其实是 base 的值与 Cell 数组里面所有 Cell 元素中的 value 值的累加, base 是个基础值,默认为 0。 cellsBusy 用来实现自旋锁,状态值只有 0 和1 ,当创建 Cell 元素, 扩容 Cell 数组或者初始化 Cell 数组时,使用 CAS 操作该变量来保证同时只有一个线程可 以进行其中之一的操作。
(2)当前线程应该访问Cell数组里面的哪一个Cell元素?
是通过 getProbe() & m 进行计算的,其中 m 是当前 cells 数组元素个数 -1 , getProbe()则用于获取当前线程中变量 threadLocalRandomProbe 的值,这个值一开始为 0,在代码longAccumulate(x, null, uncontended)
里面会对其进行初始化。
(3)如何初始化Cell数组?
//初始化Cell数组
else if (cellsBusy == 0 && cells ==as && casCellsBusy()) {
boolean init = false;
try {
if (cells == as) {
Cell[] rs = new Cell[2] ;
rs[h & l] = new Cell(x) ;
cells = rs;
init = true;
}
}finally {
cellsBusy = 0;
}
if(init) break;
}
(4)Cell数组如何扩容?
当当前元素没有达到CPU个数并且有冲突(多个线程访问了同一元素)则扩容。扩容操作也是先通过 CAS 设置 cellsBusy 为 1 ,然后才能进行扩容。 假设 CAS 成功则执行代码
Cell[] rs = new Cell[n << 1];
for(int i = 0; i < n; ++i ) rs[i] = as [i];
cells = rs;
将容量扩充为之前的 2 倍,并复制 Cell 元素到扩容后数组。 另外,扩容后 cells 数组里面除了包含复制过来的元素外,还包含其他新元素,这些元素的值目前还是 null。
(5)线程访问分配的Cell元素有冲突后如何处理?
对 CAS 失败的线程重新计算当前线程的随机值 threadLocalRandomProbe, 以减少下次访问 cells 元素时的冲突机会。
(6)如何保证线程操作被分配的Cell元素的原子性?
当前线程通过分配的 Cell 元素的 cas 函数,通过 CAS 操作,保证了当前线程更新时被分配的 Cell 元 素中 value 值的原子性。