JUC
java并发编程,为了解决线程安全问题,出现了syschronized同步方法,为了减小临界区,出现了syschronized同步代码块,为了再次减少临界区出现了原子类,原子类底层使用的是cas操作,cas操作在并发量小的时候效率可以,在并发量大的时候,只有一个线程cas操作成功,其他的线程都在cas空自旋,为了提高cas操作的效率,有两个方案,一个是分散热点,一个是使用队列削峰,分散热点的主要应用就是LondAdder,队列削峰的主要应用是AQS(抽线队列同步器)
LongAdder
是什么
LongAdder是一个分散热点解决方案。继承了Striped64,所有核心操作都位于Striped64中。
Striped64
基本的属性与API
abstract class Striped64 extends Number {
@sun.misc.Contended static final class Cell {
volatile long value;
Cell(long x) { value = x; }
//Cell底层使用仍然是cas操作
final boolean cas(long cmp, long val) {
return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val);
}
private static final sun.misc.Unsafe UNSAFE;
private static final long valueOffset;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> ak = Cell.class;
valueOffset = UNSAFE.objectFieldOffset
(ak.getDeclaredField("value"));
} catch (Exception e) {
throw new Error(e);
}
}
}
//CPU核心数
static final int NCPU = Runtime.getRuntime().availableProcessors();
//存放Cell的哈希表,大小是2的幂
//第一次创建的时候是2,以后每次扩容都是2的倍数
transient volatile Cell[] cells;
/**
* 1、没有竞争的时候,会直接更新base
* 2、在cells正在初始化时,cells不可用,也会尝试将cas操作累加到base
*/
transient volatile long base;
/**
* 自旋锁,通过cas加锁,在创建或者时扩容时使用
* 0:表示cells数组没有处于创建,扩容cells阶段。
* 1:表示cells数组处于正在创建,扩容cells阶段。不能进行cell元素的设置
*/
transient volatile int cellsBusy;
Striped64() {
}
//cas操做base
final boolean casBase(long cmp, long val) {
return UNSAFE.compareAndSwapLong(this, BASE, cmp, val);
}
//cas操作cellBusy
final boolean casCellsBusy() {
return UNSAFE.compareAndSwapInt(this, CELLSBUSY, 0, 1);
}
//线程hash
static final int getProbe() {
return UNSAFE.getInt(Thread.currentThread(), PROBE);
}
//refresh 当前线程hash
static final int advanceProbe(int probe) {
probe ^= probe << 13; // xorshift
probe ^= probe >>> 17;
probe ^= probe << 5;
UNSAFE.putInt(Thread.currentThread(), PROBE, probe);
return probe;
}
}
源码解析
LongAdder#add
public void add(long x) {
//as是cells数组的引用
//b是base的引用
//v是cell的值
//m是cells数组的长度
//a是cell
Cell[] as; long b, v; int m; Cell a;
//如果cells数组未初始化,并发量小的时候,直接对base进行cas操作
//如果cells数组已经初始化或者对base数组进行cas操作的并发量太大,则进入if语句
//条件1:成立说明数组已经初始化(存在争用),未成立说明数组未初始化(不存在争用)
//条件2:
// 条件1不成立,即cells数组未初始化,则来到条件2
// 尝试对base进行cas操作
// 成立说明cas操作失败,未成立说明cas操作成功
if ((as = cells) != null || !casBase(b = base, b + x)) {
//来到这里可能是cells数组已经初始化 或者 是cas操作失败(需要初始化cells且对cell执行cas操作)
boolean uncontended = true;//设置当前未发生竞争
if (as == null || (m = as.length - 1) < 0 ||//cells数组未初始化,直接进入longAccumulate
(a = as[getProbe() & m]) == null ||//当前线程对应的cell未初始化(还没有其他线程在同一个未知做过累加操作),直接进入longAccumulate
!(uncontended = a.cas(v = a.value, v + x)))//cell已经初始化,但是对cell的cas操作失败(cell存在争用),进入longAccumulate【uncontended=false,发生竞争】
//默认情况下uncontended都是true,只有当在槽位a上也发生竞争的时候才会是false
longAccumulate(x, null, uncontended);
}
}
核心longAccumulate方法
Striped64#longAccumulate
//牢记这个方法的核心目的:是将数据写到cell上,写完成了就退出自旋。但是小部分可能是写到base(只有在cells数组被其他线程初始化了,当前线程就casBase)
//x:参与累加的值
//wasUncontended:是否在cell上发生没有cas竞争,
//默认为true表示没有发生cas竞争
//false代表发生了cas争用
final void longAccumulate(long x, LongBinaryOperator fn,
boolean wasUncontended) {
int h;//线程hash
//条件成立,说明当前线程还未发配hash值
if ((h = getProbe()) == 0) {
//给当前线程分配hash值
ThreadLocalRandom.current(); // force initialization
//取出当前线程的hash值
h = getProbe();
//
wasUncontended = true;
}
//表示扩容意向,false一定不会扩容,true可能会扩容
boolean collide = false; // True if last slot nonempty
//自旋:目的是对cell进行cas操作累加
for (;;) {
//as是cells数组的引用
//a是当前线程命中的cell
//n是cells数组的长度
//v是期望值
Cell[] as; Cell a; int n; long v;
//Step1:cells数组已经初始化,当前线程应该将数据写入到对应的cell中,[cell可能已经初始化,也可能未初始化分条件判断]{猜测,继续重试对cells数组cas操作,或者扩容}
if ((as = cells) != null && (n = as.length) > 0) {
//来到这里的,要么是命中槽位为空,要么是发生cas竞争
//Step1.1:获取当前线程所在的槽位cell,且当前的槽位为空
if ((a = as[(n - 1) & h]) == null) {//命中槽位为空的情况,需要new Cell的过程
if (cellsBusy == 0) { // cells数组处于没有创建,扩容的阶段
Cell r = new Cell(x); // Optimistically create
if (cellsBusy == 0 && casCellsBusy()) {//修改cellBusy状态为1,表示正在操作cells(还没把数据写到cell,写完在设置为0)
boolean created = false;//自旋退出标志,设置cell成功了就退出
try { // Recheck under lock
Cell[] rs; int m, j;
//条件1,条件2 恒成立
//rs[j = (m - 1) & h] == null,为了防止其他线程初始化过该位置,导致丢失数据
if ((rs = cells) != null &&
(m = rs.length) > 0 &&
rs[j = (m - 1) & h] == null) {
rs[j] = r;//给cells槽位上赋值
created = true;//赋值成功,设置标志,退出循环
}
} finally {
cellsBusy = 0;
}
if (created)
break;
continue; // Slot is now non-empty
}
}
collide = false;
}
//前置条件:当前线程所在的cells槽位不为空,cell已经初始化
//Step1.2:在add方法中发生了竞争的情况
//进来这个方法,默认情况下uncontended都是true,只有当在槽位a上也发生竞争的时候才会是false
else if (!wasUncontended) // CAS already known to fail
//设置未发生竞争,再次自旋重置线程hash
wasUncontended = true; // Continue after rehash
//Step1.3:当前线程重置hash值,然后新命中的槽位不为空,重试cas操作,重试1次;rehash之后,如果得到的槽位还是有值,则再次来这里,重试第2次;
else if (a.cas(v = a.value, ((fn == null) ? v + x :
fn.applyAsLong(v, x))))
break;//cas成功了退出自旋
//当前的大if下的主要操作是扩容了
//前置条件:cas操作失败(两次对cell的cas操作失败,外层的add和现在的Step1.3,原因都是由于cell发生了cas竞争,此时需要扩容cells数组,减少竞争)
//Step1.4:判断是否需要扩容:
//n >= NCPU,当前的cells数组长度已经大于cpu核心,不需要扩容
//cells != as true -> 其他线程已经扩容过了,当前线程rehash之后重试即可
else if (n >= NCPU || cells != as)
//扩容意向改为false,表示不扩容了
collide = false; // At max size or stale
// Step1.5:
//!collide true -> 扩容意向设置为true,但是不一定扩容,当前celss可能需要扩容
else if (!collide)
collide = true;
//Step1.6:collide为true,需要扩容。
//判断cells数组没有上锁,cas操作给cells数组上锁
else if (cellsBusy == 0 && casCellsBusy()) {
//扩展cells数组
try {
//两个线程同时道道Step1.6,其中一个拿到锁,扩容结束后
//另外一个进入扩容,如果不加判断,则会覆盖丢失数据,所以需要cells == as
if (cells == as) { // Expand table unless stale
Cell[] rs = new Cell[n << 1];
for (int i = 0; i < n; ++i)
rs[i] = as[i];
cells = rs;
}
} finally {
//释放锁
cellsBusy = 0;
}
//不需要扩容
collide = false;
//退出本层循环,重新开始Step1,获取新的下标,重新对槽位赋值
continue; // Retry with expanded table
}
//扩容结束或之前发生竞争,重置当前线程的hash值
h = advanceProbe(h);
}
//下面的主要内容是初始化cells,并且累加数据
//前置条件:cells数组未初始化。应该对cells数组初始化,并且将线程的数据写到cell上
//Step2:cells数组未加锁【cellsBusy:0未加锁,1加锁】,在并发的情况下,如果要对cells数组进行初始化或者扩容需要上锁
//条件1:表示未对cells上锁
//条件2;cells == as?可能存在其他线程已经在Step1将cells初始化,从而cells与as不相同了,所以要判断是否cells还是当初的那个cells【多线程问题】
//如果未加锁,当前的cells引用与cells相同【有点向cas】,则调用casCellsBusy将cellsBusy设置为1,加上锁
//条件3:返回true,表示获取锁成功,cellsBusy 修改为1,表示cells上锁
else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
boolean init = false;
try { // Initialize table
//Step2.1:
//cells == as?防止其他线程已经初始化了,再次初始化,丢失数据
//可能存在两个线程一起都执行完cellsBusy == 0 && cells == as,其中一个让出了cpu,另一个得到锁,执行完了下面try-finally,cells被修改
//而另外一个线程重新得到了锁,进入了try-finally,此时如果不加判断,会导致cells重新被覆盖
if (cells == as) {
Cell[] rs = new Cell[2];
rs[h & 1] = new Cell(x);
cells = rs;
init = true;
}
} finally {
cellsBusy = 0;
}
if (init)
break;
}
//前置条件:如果cellsBusy加锁失败,表示其他线程正在初始化 cells,所以当前线程将值累加到base上。
// 1、当前的cellsBusy处于加锁状态,拿锁失败。当前的线程需要将数据累加到base
//2、自己进入Step2.1:发现自己拿到的as与最新的cells不同,cells被其他线程修改了。当前的线程需要将数据累加到base(也就是说,如果初始化失败,则会累加到base上)
//Step3:
else if (casBase(v = base, ((fn == null) ? v + x :
fn.applyAsLong(v, x))))
break; // Fall back on using base
}
}
longAccumulate方法小结
public class MyLongAdder {
final void longAccumulate(long x, LongBinaryOperator fn,
boolean wasUncontended) {
//todo:获取线程hash,线程hash为0则为其分配hash
//自旋
for(;;){
if (cells数组已经初始化){
if (cell没有初始化){
}
来到这里,说明cell已经初始化
else if(在cell上发生了cas竞争){
}
else if(经过refresh hash 来到这里对cell再次进行cas操作){
操作成功,退出自旋
}
来到这里,说明还是对cell的cas失败,说明得给cells扩容了,降低竞争的可能性
else if(判断cells 是否 达到 CPU核心数){
达到CPU核心数,拒绝再次扩容
}
else if (拿到cellsBusy锁,对cells进行操作){
对cells进行扩容
}
再次cas失败,或者扩容结束,重置当前线程hash。
if 结束
}
else{
cells数组没有初始化,else中做的就是初始化
if(拿到cellsBusy锁,没有其他线程初始化了cells数组,对其进行初始化){
初始化cells数组
}
else if(初始化失败){
当前前程将数据累加到base上
}
}
}
}
}