上一篇
1.初识sun.msic.Unsafe类:CAS操作的核心类
java不能直接访问操作系统底层,而是通过native本地方法来访问。Unsafe类提供了硬件级别的原子操作
juc并发包,即java.util.concurrent包,是JDK的核心工具包,是JDK1.5之后,由 Doug Lea实现并引入。整个java.util.concurrent包,按照功能可以大致划分如下:
- juc-locks 锁框架
- juc-atomic 原子类框架
- juc-sync 同步器框架
- juc-collections 集合框架
- juc-executors 执行器框架
初识Unsafe类
public final class Unsafe {
private Unsafe() {
} //私有化构造器,外界不能访问
static {
theUnsafe = new Unsafe();
}
//提供反射获取Unsafe实例的方法
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
}
return theUnsafe;
}
//获取var1的实际地址偏移值,是一个native本地方法
public native long objectFieldOffset(Field var1);
/**
* cas更新var2对应的属性值
*
* @param var1 操作的对象
* @param var2 操作的对象属性地址相对偏移值,通过var1和var2获取当前对象属性的当前值
* @param var4 期望值
* @param var5 新值
*/
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
//通过target对象和对象中元素的偏移地址在内存中找到指定元素的实际值
public native int getIntVolatile(Object target, long offset);
//获取target对象中相对偏移offset地址的值
public native int getInt(Object target, long offset);
/**
* @param target 指定内存对象
* @param offset 该内存对象中需要操作的对象偏移量
* @param expectValue 期望值expectValue
* @param newValue 新值newValue
* @return true cas更新成功 false cas更新失败
*/
public final native boolean compareAndSwapInt(Object target, long offset, int expectValue, int newValue);
/**
* getAndAddInt方法解析:拿到内存位置的最新值v,使用CAS尝试修将内存位置的值修改为目标值v+delta,如果修改失败
* 则获取该内存位置的新值v,然后继续尝试,直至修改成功
*
* @param target 内存对象
* @param offset 该内存对象中需要操作的对象偏移量
* @param var4 增量
*/
public final int getAndAddInt(Object target, long offset, int var4) {
int var5;
//while循环尝试更新offset对应的对象元素值,知道成功才会退出,期间一直占据cpu
do {
//通过对象地址和相对地址获取内存数据var5即变量"value"
//不停的while循环开销很大
var5 = this.getIntVolatile(target, offset);
} while (!this.compareAndSwapInt(target, offset, var5, var5 + var4));
return var5;
}
public native long allocateMemory(long l); //分配内存
public native long reallocateMemory(long l, long l1); //扩充内存
public native void freeMemory(long l); //释放内存
//返回指定类型数组的第一个元素相对于数组起始地址的偏移值,例如arrayClass=String[].class
public native int arrayBaseOffset(Class arrayClass);
// 获取用户给定数组寻址的换算因子,也就是数组中元素的增量地址,是2的幂次方
public native int arrayIndexScale(Class<?> var1); //将arrayBaseOffset与arrayIndexScale配合使用,可以定位数组中每个元素在内存中的位置。
//eg:获取array数组中下标为i的元素
//计算i位置元素的地址偏移
//long ABASE = unsafe.arrayBaseOffset(String[].class); //计算String[].class的首元素地址偏移
//int SCALE = unsafe.arrayIndexScale(String[].class); //计算每个String类型元素的增量地址
//int ASHIFT = 31 - Integer.numberOfLeadingZeros(SCALE);
//long offset = ABASE + i * SCALE; //计算偏移地址(method1)
//long offset = ABASE + i << ASHIFT; //计算偏移地址(method2)
//String obj= (String) unsafe.getObject(array, ((long) i << ASHIFT) + ABASE);
//设置obj对象中offset偏移地址对应的整型field的值为指定值。这是一个有序或者有延迟的<code>putIntVolatile</cdoe>方法,并且不保证值的改变被其他线程立即看到。只有在field被<code>volatile</code>修饰并且期望被意外修改的时候使用才有用。
public native void putOrderedInt(Object obj, long offset, int value);
//设置obj对象中offset偏移地址对应的整型field的值为指定值。支持volatile store语义
public native void putIntVolatile(Object obj, long offset, int value);
//释放被park创建的在一个线程上的阻塞
public native void unpark(Thread thread);
//阻塞一个线程直到unpark出现、线程被中断或者timeout时间到期。如果一个unpark调用已经出现了,这里只计数。timeout为0表示永不过期
public native void park(boolean isAbsolute, long time);
}
2.CAS:Compare And Swap比较并替换
概念:是使用一个期望值和一个变量的当前值进行比较,如果当前变量的值与我们期望的值相等,就使用一个新值替换当前变量的值。CAS算法的过程是这样:它包含三个参数 CAS(V,E,N)。V表示要更新的变量,E表示预期的值,N表示新值。仅当V值等于E值时,才会将V的值设置成N,否则什么都不做。最后CAS返回当前V的值。CAS算法需要你额外给出一个期望值,也就是你认为现在变量应该是什么样子,如果变量不是你想象的那样,那说明已经被别人修改过。你就重新读取,再次尝试修改即可。
CAS操作被封装在Unsafe类下,由于需要直接操作内存值"value",通过unsafe获取内存值"value"的内存地址(该地址是相对于AtomicaInteger对象的内存地址,Java会将AtomicaInteger对象的内存地址 + valueOffset映射成实际内存地址)。由于是相对内存地址,所以对于任意一个AtomicaInteger对象,该地址是固定的,只需要在static初始化时获取该地址即可。
CAS存在的问题和解决方案
CAS虽然很高效的解决了原子操作问题,但是CAS仍然存在三大问题:
1.ABA问题:因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A
2.循环时间开销大:自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销(时刻a的内存值和时刻b的内存值比较,如果相等则更新,否则一直循环)
3.乐 观 锁 只 能 保 证 一 个 共 享 变 量 的 原 子 操 作 。 如 果 多 一 个 或 几 个 变 量 , 乐观 锁 将 变 得 力 不 从 心 , 但 互 斥 锁 能 轻 易 解 决 , 不 管 对 象 数 量 多 少 及 对 象颗 粒 度 大 小
解决方案:
- AtomicMarkableReference 维护了boolean变量表示引用变量是否被更改过
- AtomicStampedReference 维护了int类型的变量表示版本号
3.初识原子类一:AtomicInteger
public class AtomicInteger {
//底层实现借助了Unsafe类
private static final Unsafe unsafe = Unsafe.getUnsafe();
//在这里具体即是指value这个字段在AtomicInteger类的内存中相对于该类首地址的偏移量
private static final long valueOffset;
//AtomicInteger类的核心:维护了一个int类型的共享变量
//对于CAS操作的内存对象必须具有可见性,即需要使用volitile进行修饰
//保存每次对value的读写都会从主存中读写,即真值
//volatile保证在多线程中value的值时可见的,任何一个线程修改了value的值,都会被立即回写到主内存中
//volatile保证了可见性,有序性(防止指令重排),不保证原子性
private volatile int value;
//获取value变量在该对象中的相对地址偏移值
static {
try {
valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) {
throw new Error(ex);
}
}
//使用得最多的方法:value+1,原子操作
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
//使用得最多的方法:value-1,原子操作
public final int getAndDecrement() {
return unsafe.getAndAddInt(this, valueOffset, -1);
}
/**
* 尝试cas更新,可能会成功,也可能会失败
*
* @param expect 期望值
* @param update 更新值
* @return true更新成功 false更新失败
*/
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
//通过该方法对共享变量值的改变,不一定能被其他线程立即看到。也就是说以普通变量的操作方式来写变量。即无法保证value的可见性
public final void lazySet(int newValue) {
unsafe.putOrderedInt(this, valueOffset, newValue);
}
}
4.初识原子类二:AtomicIntegerArray
public class AtomicIntegerArray {
private static final Unsafe unsafe = Unsafe.getUnsafe();
//返回指定类型数组的第一个元素地址相对于数组起始地址的偏移
private static final int base = unsafe.arrayBaseOffset(int[].class); //定值,
private static final int shift; //定值,用于计算指定下标的相对地址偏移值
private final int[] array; //底层核心
static {
//返回指定类型数组的元素占用的字节数
int scale = unsafe.arrayIndexScale(int[].class);
if ((scale & (scale - 1)) != 0) //确保scale是2的幂次方,这样就可以使用位运算计算索引位置
throw new Error("data type scale not a power of two");
//Integer.numberOfLeadingZeros(scale); //将scale转换为2进制,然后从左往右数遇到第一个非1的连续0的个数
shift = 31 - Integer.numberOfLeadingZeros(scale);
}
private long checkedByteOffset(int i) {
if (i < 0 || i >= array.length)
throw new IndexOutOfBoundsException("index " + i);
return byteOffset(i);
}
//返回数组中索引为i的元素相对于数组起始位置的偏移
private static long byteOffset(int i) {
//等价于i * scale + base <=== 2 ^ shift = scale <=== 1向左移动shift位等于scale
return ((long) i << shift) + base;
}
}
5.初识原子类三:AtomicStampedReference
public class AtomicStampedReference<V> {
private static final Unsafe UNSAFE = Unsafe.getUnsafe();//底层借助了Unsafe类的原子操作
//获取pair属性对象的地址相对偏移值
private static final long pairOffset = objectFieldOffset(UNSAFE, "pair", AtomicStampedReference.class);
//核心:底层维护了一个par对象
private volatile Pair<V> pair;
private static class Pair<T> {
final T reference; //实际的业务对象
final int stamp; //戳值,类似版本号,解决了ABA问题
private Pair(T reference, int stamp) {
this.reference = reference;
this.stamp = stamp;
}
static <T> Pair<T> of(T reference, int stamp) {
return new Pair<T>(reference, stamp);
}
}
public AtomicStampedReference(V initialRef, int initialStamp) {
pair = Pair.of(initialRef, initialStamp);
}
//同时返回pair对象和stamp戳值(利用数组)
public V get(int[] stampHolder) {
Pair<V> pair = this.pair;
stampHolder[0] = pair.stamp;
return pair.reference;
}
/**
* 尝试更新pair
*
* @param expectedReference 期望值
* @param newReference 新值
* @param expectedStamp 期望戳值
* @param newStamp 新戳值
* @return true更新成功 false更新失败
*/
public boolean weakCompareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp) {
return compareAndSet(expectedReference, newReference, expectedStamp, newStamp);
}
/**
* 尝试cas更新pair
*
* @param expectedReference 期望值
* @param newReference 新值
* @param expectedStamp 期望戳值
* @param newStamp 新戳值
* @return true更新成功 false更新失败
*/
public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp) {
//pair的内存可见性保证了此刻获取到的pair一定是真实值
Pair<V> current = pair;
//判断是否可以更新:只有期望值=旧值并且期望戳值=旧戳值的时候才可以更新(cas原则)
boolean canUpdate = expectedReference == current.reference && expectedStamp == current.stamp;
//判断是否没有必要更新:如果新值=旧值并且新戳值=旧戳值的时候,更新毫无意义
boolean noNeedUpdate = newReference == current.reference && newStamp == current.stamp;
return canUpdate && (noNeedUpdate || casPair(current, Pair.of(newReference, newStamp)));
}
/**
* 更新pair
*
* @param newReference 对象的新值
* @param newStamp 对象的新戳值
*/
public void set(V newReference, int newStamp) {
Pair<V> current = pair;
if (newReference != current.reference || newStamp != current.stamp)
this.pair = Pair.of(newReference, newStamp);
}
/**
* 尝试只更新戳值
*
* @param expectedReference 期望值
* @param newStamp 新戳值
*/
public boolean attemptStamp(V expectedReference, int newStamp) {
Pair<V> current = pair;
return expectedReference == current.reference && (newStamp == current.stamp || casPair(current, Pair.of(expectedReference, newStamp)));
}
/**
* 尝试cas更新pair对象
*
* @param cmp 对象期望值
* @param val 对象新值
*/
private boolean casPair(Pair<V> cmp, Pair<V> val) {
return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
}
static long objectFieldOffset(Unsafe UNSAFE, String field, Class<?> klazz) {
try {
return UNSAFE.objectFieldOffset(klazz.getDeclaredField(field));
} catch (NoSuchFieldException e) {
NoSuchFieldError error = new NoSuchFieldError(field);
error.initCause(e);
throw error;
}
}
}
6.初识原子类四:AtomicMarkableReference
AtomicMarkableReference和AtomicStampedReference一样也可以解决ABA的问题,两者唯一的区别是:
AtomicStampedReference 是通过int 类型的版本号,而AtomicMarkableReference是通过boolean型的标识来判断数据是否有更改过。
既然有了 AtomicStampedReference 为啥还需要再提供 AtomicMarkableReference 呢?
在现实业务场景中,不关心引用变量被修改了几次,只是单纯的关心是否更改过
public class AtomicMarkableReference<V> {
private static final Unsafe UNSAFE = Unsafe.getUnsafe();
private static final long pairOffset = objectFieldOffset(UNSAFE, "pair", AtomicMarkableReference.class);
private volatile Pair<V> pair;
private static class Pair<T> {
final T reference;
final boolean mark;
private Pair(T reference, boolean mark) {
this.reference = reference;
this.mark = mark;
}
static <T> Pair<T> of(T reference, boolean mark) {
return new Pair<T>(reference, mark);
}
}
public V get(boolean[] markHolder) {
Pair<V> pair = this.pair;
markHolder[0] = pair.mark;
return pair.reference;
}
/**
* @param expectedReference the expected value of the reference
* @param newReference the new value for the reference
* @param expectedMark the expected value of the mark
* @param newMark the new value for the mark
* @return {@code true} if successful
*/
public boolean weakCompareAndSet(V expectedReference, V newReference, boolean expectedMark, boolean newMark) {
return compareAndSet(expectedReference, newReference, expectedMark, newMark);
}
/**
* @param expectedReference the expected value of the reference
* @param newReference the new value for the reference
* @param expectedMark the expected value of the mark
* @param newMark the new value for the mark
* @return {@code true} if successful
*/
public boolean compareAndSet(V expectedReference, V newReference, boolean expectedMark, boolean newMark) {
Pair<V> current = pair;
return expectedReference == current.reference &&
expectedMark == current.mark &&
((newReference == current.reference &&
newMark == current.mark) ||
casPair(current, Pair.of(newReference, newMark)));
}
/**
* Unconditionally sets the value of both the reference and mark.
*
* @param newReference the new value for the reference
* @param newMark the new value for the mark
*/
public void set(V newReference, boolean newMark) {
Pair<V> current = pair;
if (newReference != current.reference || newMark != current.mark)
this.pair = Pair.of(newReference, newMark);
}
/**
* @param expectedReference the expected value of the reference
* @param newMark the new value for the mark
* @return {@code true} if successful
*/
public boolean attemptMark(V expectedReference, boolean newMark) {
Pair<V> current = pair;
return expectedReference == current.reference &&
(newMark == current.mark || casPair(current, Pair.of(expectedReference, newMark)));
}
private boolean casPair(Pair<V> cmp, Pair<V> val) {
return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
}
static long objectFieldOffset(sun.misc.Unsafe UNSAFE, String field, Class<?> klazz) {
try {
return UNSAFE.objectFieldOffset(klazz.getDeclaredField(field));
} catch (NoSuchFieldException e) {
NoSuchFieldError error = new NoSuchFieldError(field);
error.initCause(e);
throw error;
}
}
}
7.初识原子类五:AtomicLong
AtomicLong是Java1.5时的一个基于CAS的原子类,通过CAS算法提供了非阻塞的原子性操作,但是在超高并发下AtomicLong的性能就会非常低下。
在超高并发下AtomicLong的性能并不高的原因:
因为N多线程去操作一个变量会造成大量线程CAS失败,
然后一直处于自旋状态(一直占用CPU),导致严重浪费CPU资源,降低了并发性
使用场景:低并发下全局计数器;序列号生成器,累加并返回结果
瓶颈:当并发量高的时候,性能急剧下降
public class AtomicLong {
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;//value属性的相对地址偏移量
private volatile long value;
//获取value属性的相对地址偏移量
static {
try {
valueOffset = unsafe.objectFieldOffset(AtomicLong.class.getDeclaredField("value"));
} catch (Exception ex) {
throw new Error(ex);
}
}
public final void lazySet(long newValue) {
unsafe.putOrderedLong(this, valueOffset, newValue);
}
public final long getAndSet(long newValue) {
return unsafe.getAndSetLong(this, valueOffset, newValue);
}
public final boolean compareAndSet(long expect, long update) {
return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
}
public final boolean weakCompareAndSet(long expect, long update) {
return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
}
public final long getAndIncrement() {
return unsafe.getAndAddLong(this, valueOffset, 1L);
}
public final long getAndDecrement() {
return unsafe.getAndAddLong(this, valueOffset, -1L);
}
public final long incrementAndGet() {
return unsafe.getAndAddLong(this, valueOffset, 1L) + 1L;
}
public final long addAndGet(long delta) {
return unsafe.getAndAddLong(this, valueOffset, delta) + delta;
}
public final long getAndUpdate(LongUnaryOperator updateFunction) {
long prev, next;
do {
prev = get();
next = updateFunction.applyAsLong(prev);
} while (!compareAndSet(prev, next));
return prev;
}
public final long getAndAccumulate(long x, LongBinaryOperator accumulatorFunction) {
long prev, next;
do {
prev = get();
next = accumulatorFunction.applyAsLong(prev, x);
} while (!compareAndSet(prev, next));
return prev;
}
public final long accumulateAndGet(long x, LongBinaryOperator accumulatorFunction) {
long prev, next;
do {
prev = get();
next = accumulatorFunction.applyAsLong(prev, x);
} while (!compareAndSet(prev, next));
return next;
}
}
8.初识原子类六:LongAdder
参考链接:blog.csdn.net/weixin_4359…
LongAdder为什么这么快:
设计思想上,LongAdder采用"分段"的方式降低CAS失败的频次
AtomicLong中有个内部变量value保存着实际的long值,所有的操作都是针对该变量进行。也就是说,高并发环境下,value变量其实是一个热点数据,也就是N个线程竞争一个热点
LongAdder的基本思路就是分散热点,将value值的新增操作分散到一个数组中,不同线程会命中到数组的不同槽中,各个线程只对自己槽中的那个value值进行CAS操作,这样热点就被分散了,冲突的概率就小很多
使用Contended注解来消除伪共享(在 LongAdder 的父类 Striped64 中存在一个 volatile Cell[] cells; 数组,其长度是2 的幂次方,每个Cell都使用 @Contended 注解进行修饰,而@Contended注解可以进行缓存行填充,从而解决伪共享问题。伪共享会导致缓存行失效,缓存一致性开销变大)
@sun.misc.Contended static final class Cell { }
看上去LongAdder的性能全面超越了AtomicLong,而且阿里巴巴开发手册也提及到 推荐使用 LongAdder 对象,比 AtomicLong 性能更好(减少乐观锁的重试次数) ,但是我们真的就可以舍弃掉LongAdder了吗?
当然不是,我们需要看场景来使用,如果是并发不太高的系统,使用AtomicLong
可能会更好一些,而且内存需求也会小一些。
我们看过sum()方法后可以知道LongAdder在统计的时候如果有并发更新,可能导致统计的数据有误差。
而在高并发统计计数的场景下,才更适合使用LongAdder。
在并发量较低的环境下,线程冲突的概率比较小,自旋的次数不会很多。
但是,高并发环境下,N个线程同时进行自旋操作,会出现大量失败并不断自旋的情况,此时AtomicLong的自旋会成为瓶颈。
这就是LongAdder引入的初衷——解决高并发环境下AtomicLong的自旋瓶颈问题。特别是写多的场景
LongAdder的原理是,在最初无竞争时,只更新base的值,当有多线程竞争时通过分段的思想,让不同的线程更新不同的段,
最后把这些段相加就得到了完整的LongAdder存储的值。(空间换时间,分散热点数据)
public class LongAdder extends Striped64 {
private static final long BASE; //base变量的相对地址偏移
private static final long CELLSBUSY; //cellsBusy变量的相对地址偏移
private static final long PROBE; //threadLocalRandomProbe变量的相对地址偏移
//cpu可用核数,决定了槽数组cells的大小
static final int NCPU = Runtime.getRuntime().availableProcessors();
//底层使用数组存放,2倍扩容
transient volatile Cell[] cells; // cells数组,存储各个段的值
//基数,在两种情况下会使用:
//1.没有遇到并发竞争时,直接使用base累加数值
//2.初始化cells数组时,必须要保证cell数组只被初始化一次(即只有一个线程可以对cells初始化),其他竞争失败的线 //程会将数值累加base上
transient volatile long base; // 最初无竞争时使用的,也算一个特殊的段
//锁标识:
//cells初始化或扩容时,此标识为1(加锁状态),初始化或扩容完成后,将此标识设置为0-无锁状态
// 标记当前是否有线程在创建或扩容cells,或者在创建Cell;通过CAS更新该值,相当于是一个锁
transient volatile int cellsBusy;
public long sum() { //求和,最终值
Cell[] as = cells;
Cell a;
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 add(long x) {
Cell[] as;
long b, v;
int m;
Cell a;
if ((as = cells) != null //cells为空,需要进行cas;不为空表示已经出现了竞争,已经创建了cells
|| !casBase(b = base, b + x)) { //如果cas失败返回false,说明其他线程抢先一步修改了base,正在出现竞争
//true表示当前竞争还不激烈
//false表示竞争激烈,多个线程hash到同一个cell,可能要扩容
boolean uncontended = true;
if (as == null || (m = as.length - 1) < 0
//一个线程对应一个probe,它是通过随机数生成的一个值,对于一个确定的线程这个值是固定的,除非刻意修改它
//通过prope和size-1做位与运算散列到cells对应的索引位置,如果为空说明该线程所在cells中的cell为空,应初始化一个Cell
//可能会有多个线程的cells索引位置相同,存在竞争
|| (a = as[getProbe() & m]) == null
//如果执行到这里,说明当前线程对应的cell不为空,尝试更新当前cell中的value。
//如果cell.cas成功, 也就是uncontended=true表示竞争不激烈;
//如果失败了, 也就是uncontended=false,说明这一个cell上一定有其他线程在同步修改cell,竞争激烈
|| !(uncontended = a.cas(v = a.value, v + x)))
longAccumulate(x, null, uncontended);
}
}
// wasUncontended竞争标识,false代表有竞争。只有cells初始化之后,并且当前线程CAS竞争修改失败,才会是false
final void longAccumulate(long x, LongBinaryOperator fn, boolean wasUncontended) {
int h;
//给当前线程分配hash值
if ((h = getProbe()) == 0) {//如果prope=0,0与任何数取模都是0,会固定到数组第一个位置,所以这里做了优化
ThreadLocalRandom.current(); //强制初始化,防止prope还是为0
h = getProbe(); //重新获取prope
wasUncontended = true; //都未初始化,肯定还不存在竞争激烈(hash值被重置就好比一个全新的线程一样,所以设置为true)
}
boolean collide = false; // 表示扩容意向,false 一定不会扩容,true可能会扩容
for (; ; ) {
Cell[] as;
Cell a;
int n;
long v;
//case1:cells已经被初始化过了
if ((as = cells) != null && (n = as.length) > 0) {
if ((a = as[(n - 1) & h]) == null) { //如果该线程对应的cell还没有被初始化
//如果还没有上锁:也就是当前无其它线程在扩容cells,也没有线程在创建cell
if (cellsBusy == 0) {
Cell r = new Cell(x);
// 再次检测cellsBusy,并尝试更新它为1,相当于当前线程加锁
if (cellsBusy == 0 && casCellsBusy()) { //只有锁标志位为0并且CAS成功才能进入下面的逻辑
boolean created = false;
try {
Cell[] rs;
int m, j;
// 重新获取cells,并找到当前线程hash到cells数组中的位置
// 这里一定要重新获取cells,因为rs并不在锁定范围内
// 有可能已经扩容了,这里要重新获取
if ((rs = cells) != null &&
(m = rs.length) > 0 &&
rs[j = (m - 1) & h] == null) {
rs[j] = r; // 把上面新建的Cell放在cells的j位置处
created = true;
}
} finally {
cellsBusy = 0;// 相当于释放锁
}
if (created) // 创建成功了就返回,值已经放在新建的Cell里面了
break;
continue; // Slot is now non-empty
}
}
collide = false; // 标记当前未出现冲突
} else if (!wasUncontended) // 如果为false说明刚才已经竞争失败了,那么高并发情况下下次尝试大概率还是失败,所以没必要马上cas自旋继续尝试,还不如修改probe然后在尝试
wasUncontended = true; // 简单地设为true,相当于简单地自旋一次,修改线程的probe再重新尝试
// 再次尝试CAS更新当前线程所在Cell的值,如果成功了就返回
else if (a.cas(v = a.value, ((fn == null) ? v + x : fn.applyAsLong(v, x))))
break;
// 如果cells数组的长度达到了CPU核心数,或者cells扩容了
// 如果当前数组的长度已经大于了CPU的核数,就会再次设置扩容意向collide=false,
// 这里的意义是保证扩容意向为false后不再继续往后执行扩容操作。
else if (n >= NCPU || cells != as)
collide = false;
else if (!collide)
collide = true;
// CPU数量问题:每个线程会通过线程对cells[threadHash % cells.length] 位置的Cell对象中的value做累加,
// 这样相当于将线程绑定到了cells中的某个cell对象上,如果超过CPU数量的时候就不再扩容是因为CPU的数量代表了机器处理能力,
// 当超过CPU数量时,多出来的cells数组元素没有太大作用。??????
// 明确出现冲突了,尝试占有锁,并扩容
else if (cellsBusy == 0 && casCellsBusy()) {
try {
if (cells == as) { // 检查是否有其它线程已经扩容过了
Cell[] rs = new Cell[n << 1]; //2倍扩容
for (int i = 0; i < n; ++i)
rs[i] = as[i];
cells = rs;
}
} finally {
cellsBusy = 0;//释放锁
}
collide = false; //已解决冲突
continue;// 使用扩容的数组再次尝试
}
// 更新失败或者达到了CPU核心数,重新生成probe,并重试
h = advanceProbe(h);
}
//case2:cells没有加锁并且没有被初始化,则尝试对它加锁,并初始化cells数组
else if (cellsBusy == 0
&& cells == as //判断cells == as,这个代表当前线程到了这里获取的cells还是之前的一致
&& casCellsBusy()) {
//成功获取锁后,准备初始化
boolean init = false;
try {
if (cells == as) { // 检查是否有其它线程已经扩容过了
Cell[] rs = new Cell[2];
rs[h & 1] = new Cell(x);
cells = rs;
init = true;
}
} finally {
cellsBusy = 0; //释放锁
}
if (init)
break;
}
//如果在初始化过程中,另一个线程ThreadB也进入了longAccumulate方法,就会进入分支
//case3:cells正在进行初始化,则尝试直接在base上进行累加操作
else if (casBase(v = base, ((fn == null) ? v + x : fn.applyAsLong(v, x))))
break;
}
}
final boolean casCellsBusy() { //尝试cas更新cellsBusy的值从0到1,类似上锁的概念
return UNSAFE.compareAndSwapInt(this, CELLSBUSY, 0, 1);
}
}
LongAdder内部原理图
9.如何获取Unsafe实例
1、从getUnsafe方法的使用限制条件出发,通过Java命令行命令-Xbootclasspath/a把调用Unsafe相关方法的类A所在jar包路径追加到默认的bootstrap路径中,使得A被引导类加载器加载,从而通过Unsafe.getUnsafe方法安全的获取Unsafe实例。
java -Xbootclasspath/a:${path} // 其中path为调用Unsafe相关方法的类所在jar包路径
2、通过反射获取单例对象theUnsafe。
public class UnsafeInstance {
public static Unsafe reflectGetUnsafe() {
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
return (Unsafe) field.get(null);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}