LongAdder源码分析
AtomicLong是作用是对长整形进行原子操作,显而易见,在java1.8中新加入了一个新的原子类LongAdder,该类也可以保证Long类型操作的原子性,相对于AtomicLong,LongAdder有着更高的性能和更好的表现,可以完全替代AtomicLong的来进行原子操作。
AtomicLong的代码很简单,下面仅以incrementAndGet()为例,对AtomicLong的原理进行说明。
incrementAndGet()源码如下:
public final long incrementAndGet() {
for (;;) {
// 获取AtomicLong当前对应的long值
long current = get();
// 将current加1
long next = current + 1;
// 通过CAS函数,更新current的值
if (compareAndSet(current, next))
return next;
}
} // value是AtomicLong对应的long值
private volatile long value;
// 返回AtomicLong对应的long值
public final long get() {
return value;
} public final boolean compareAndSet(long expect, long update) {
return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
} compareAndSet()的作用是更新AtomicLong对应的long值。它会比较AtomicLong的原始值是否与expect相等,若相等的话,则设置AtomicLong的值为update。
比AtomicLong更高效的LongAdder
AtomicLong的实现方式是内部有个value 变量,当多线程并发自增,自减时,均通过cas 指令从机器指令级别操作保证并发的原子性。
AtomicLong的实现方式是内部有个value 变量,当多线程并发自增,自减时,均通过CAS 指令从机器指令级别操作保证并发的原子性。
LongAdder是jdk8新增的用于并发环境的计数器,目的是为了在高并发情况下,代替AtomicLong/AtomicInt,成为一个用于高并发情况下的高效的通用计数器。
高并发下计数,一般最先想到的应该是AtomicLong/AtomicInt,AtmoicXXX使用硬件级别的指令 CAS 来更新计数器的值,这样可以避免加锁,机器直接支持的指令,效率也很高。但是AtomicXXX中的 CAS 操作在出现线程竞争时,失败的线程会白白地循环一次,在并发很大的情况下,因为每次CAS都只有一个线程能成功,竞争失败的线程会非常多。失败次数越多,循环次数就越多,很多线程的CAS操作越来越接近 自旋锁(spin lock)。计数操作本来是一个很简单的操作,实际需要耗费的cpu时间应该是越少越好,AtomicXXX在高并发计数时,大量的cpu时间都浪费会在 自旋 上了,这很浪费,也降低了实际的计数效率。
// jdk1.8的AtomicLong的实现代码,这段代码在sun.misc.Unsafe中
// 当线程竞争很激烈时,while判断条件中的CAS会连续多次返回false,这样就会造成无用的循环,循环中读取volatile变量的开销本来就是比较高的
// 因为这样,在高并发时,AtomicXXX并不是那么理想的计数方式
public final long getAndAddLong(Object o, long offset, long delta) {
long v;
do {
v = getLongVolatile(o, offset);
} while (!compareAndSwapLong(o, offset, v, v + delta));
return v;
}
说LongAdder比在高并发时比AtomicLong更高效,这么说有什么依据呢?LongAdder是根据ConcurrentHashMap这类为并发设计的类的基本原理——锁分段,来实现的,它里面维护一组按需分配的计数单元,并发计数时,不同的线程可以在不同的计数单元上进行计数,这样减少了线程竞争,提高了并发效率。本质上是用空间换时间的思想,不过在实际高并发情况中消耗的空间可以忽略不计。
现在,在处理高并发计数时,应该优先使用LongAdder,而不是继续使用AtomicLong。当然,线程竞争很低的情况下进行计数,使用Atomic还是更简单更直接,并且效率稍微高一些。
既要看单线程的执行结果,还要看多线程对他的影响。
每次操作时候都要看局部变量是不是等于成员变量(判断是否没有别的线程干扰),最后再把局部变量赋值给成员变量完成修改。成员变量的修改都是CAS。
@SuppressWarnings("serial")
abstract class Striped641 extends Number1 {
static final int NCPU = Runtime.getRuntime().availableProcessors();
transient volatile Cell[] cells;// cell数组,长度一样要是2^n,可以类比为jdk1.7的ConcurrentHashMap中的segments数组
// 累积器的基本值 ,没有遇到并发的情况,直接使用base,速度更快;
transient volatile long base;//cas更新
// 自旋标识,在对cells进行初始化,或者后续扩容时,需要通过CAS操作把此标识设置为1(busy,忙标识,相当于加锁), 取消busy时可以直接使用cellsBusy = 0,相当于释放锁
transient volatile int cellsBusy;//旋转锁
Striped641() {
}
final boolean casBase(long cmp, long val) {//原子更新base
return UNSAFE.compareAndSwapLong(this, BASE, cmp, val);
}
// 使用CAS将cells自旋标识更新为1,更新为0时可以不用CAS(赋值为0肯定是只有一个线程在赋值为0),直接使用cellsBusy就行
final boolean casCellsBusy() {//原子更新cellsBusy从0到1,以获取锁。
return UNSAFE.compareAndSwapInt(this, CELLSBUSY, 0, 1);
} // 下面这两个方法是ThreadLocalRandom中的方法,不过因为包访问关系,这里又重新写一遍 // probe翻译过来是探测/探测器/探针这些,不好理解,它是ThreadLocalRandom里面的一个属性,
// 不过并不影响对Striped64的理解,这里可以把它理解为线程本身的hash值
static final int getProbe() {
return UNSAFE.getInt(Thread.currentThread(), PROBE);
}
// 相当于rehash,重新算一遍线程的hash值
static final int advanceProbe(int probe) {
probe ^= probe << 13; // xorshift
probe ^= probe >>> 17;
probe ^= probe << 5;
UNSAFE.putInt(Thread.currentThread(), PROBE, probe);//CAS设置当前线程的threadLocalRandomProbe
return probe;
} //x:要增加的数。fn:执行函数。uncontended=false表示更新失败了,=true表示没有这个线程的Cell(不可能是更新成功了,更新成功就进不来这里)。
final void longAccumulate(long x, LongBinaryOperator fn, boolean wasUncontended) {//开始分段更新:1.base更新失败。2.前面分段更新不行或失败。
//新建,更新,扩容,初始化,更新base。 int h;//线程hash值
// 看下ThreadLocalRandom是否初始化。如果当前线程的threadLocalRandomProbe为0,说明当前线程是第一次进入该方法,
if ((h = getProbe()) == 0) {// 当前线程hash值=0,
ThreadLocalRandom.current(); //初始化当前线程的PROBE值不为0,
h = getProbe();
wasUncontended = true;//下面的cas语句走不走,还是间隔一个for循环在cas uncontended=false代表存在争用,uncontended=true代表不存在争用。
} boolean collide = false; //下面的扩容语句走不走,还是间隔一个for循环在扩容 。collide=true代表cas有冲突,collide=false代表cas无冲突 for (;;) {
Cell[] as;//局部变量,线程执行时候,局部变量不会变,成员变量会改变(修改属性地址不变,重新new地址才改变)。as一进来就赋值后面没有更改过。
Cell a;//线程对应的cell,a一进来就赋值后面没有更改过。
//cellsBusy没有局部变量,直接使用成员变量。是一个锁。
int n;
long v; //------------------------------------------重要---多线程时候,在一个线程的周期里面,前一个指令的判断(被另一个线程修改)现在不一定成立了-----------------------------------------------------//
/*每次执行真正操作时候,有可能刚才判断的条件全部不成立了,就要重来,那么刚才判断条件有什么用:不冲突有用。
执行成功时候:在锁住期间,刚才进来的判断都成立,近似于单线程操作,或者别的操作了,但是不影响现在要操作的条件。*/
/*多线程同时判断3个if,有可能一个线程判断第一个if不成立,但是判断第二个if时候,第一个if条件又成立了。走到后面的判断,只能说刚才前面的判断不成立,现在前面的判断不一定不成立了。
所以进入一个if:要看2个判断,之前判断是什么,现在判断是什么,才进入这个if条件 */
/*casCellsBusy()用于锁住这个cells,别的线程不能扩容和初始化和新建cell(但是可以cas更新存在的cell)。但是casCellsBusy()前后的条件不一定再次成立了,所以锁住之后要再次判断刚才的条件
,多线程时候上一次的判断现在不一定成立了。*/
/*局部变量,线程执行时候,局部变量不会变,成员变量会改变(修改属性地址不变,重新new地址才改变)。as一进来就赋值后面没有更改过。*/
/*在一个线程里面:1.已经有cells,要么新建(新建时候看是不是空),要么更新(要看是不是原值),要么扩容(要看cells有没有变化)。2.没有cells就去初始化(初始话时候再看是不是空)。3.初始化抢不赢就去更新base。*/
//------------------------------------------重要-----多线程时候,在一个线程的判断周期里面,前一个指令的判断(被另一个线程修改)现在不一定成立了---------------------------------------------------// //as == cells,只有初始化和扩容(因为重新new)才不相等,修改值还是相等的。
//每次重新来,都会重新获取as和a,as = cells,a = as[(n - 1) & h],并且更新线程hash。 // 1.已经有分段更新了cells!=null
if ((as = cells) != null && (n = as.length) > 0) {
//1.1 没有这个线程的Cell,新建
//重新来:1.新建cell时候有人占用cell。2.新建cell时候位置不为空。
if ((a = as[(n - 1) & h]) == null) {
if (cellsBusy == 0) { // cellsBusy=1表示有人在修改Cells数组(修改Cell从null到new Cell,扩容,初始化),CAS更新一个已经存在的Cell不用判断cellsBusy。
Cell r = new Cell(x); //这期间其他线程可以做很多事
if (cellsBusy == 0 && casCellsBusy()) {// cellsBusy是0就进来,然后变成cellsBusy=1,别的进不来。 //不可能多个线程同时进这里面来。 /*锁住cells,但是前面判断a=null,现在不一定a=null了,因为在前面判断到锁住cells期间cells有可能改变了,并且cellsBusy从0变到1又变到0。所以锁住之后在判断是不是空。*/ /*每次执行真正操作时候,有可能刚才判断的条件全部不成立了,就要重来,那么刚才判断条件有什么用:不冲突有用。
执行成功时候:在锁住期间,刚才进来的判断都成立,近似于单线程操作,或者别的操作了,但是不影响现在要操作的条件。*/ boolean created = false;
try {
Cell[] rs;
int m, j;
// 再次判断没有这个cell, 前面if判断了是空,走到这里时候有可能别人放进去了并且cellsBusy从0变到1再变到0了。如果不是null了,就不放,下次再来(直接更新)。
if ((rs = cells) != null && (m = rs.length) > 0 && rs[j = (m - 1) & h] == null) {
rs[j] = r;// 赋值
created = true;// 创建完成,退出,不用重新来了。
}
} finally {
cellsBusy = 0;//释放锁
}
if (created)// 创建完成,退出
break;
continue; // 这个线程没有成功创建,肯定重头再来
}
}
collide = false;// cell不存在,但是有人修改cells,collide = false,
}
//1.2有这个线程的cell
//wasUncontended=false重新来
else if (!wasUncontended) // wasUncontended=false表示更新失败了,再来,wasUncontended=true下次不进这里直接去cas更新,否则先不cas先再来一次。
wasUncontended = true;
// 1.3有这个线程的cell
//wasUncontended=true,更新失败了重新来。
else if (a.cas(v = a.value, ((fn == null) ? v + x : fn.applyAsLong(v, x))))//这个线程有Cell,去更新。
break;// 更新成功,退出。
// 1.4有这个线程的cell
//wasUncontended=true,更新失败,cells初始化扩容了,重新来
else if (n >= NCPU || cells != as) // CPU能够并行的CAS操作的最大数量是它的核心数 ,cells被改变了(扩容了肯定重新来)。
collide = false;
// 1.5有这个线程的cell
//wasUncontended=true,更新失败,cells没有初始化扩容,collide=false,重新来
else if (!collide) //=false走这里,collide=true,下次不走这里直接去扩容否则先不去扩容先再来一次。
collide = true;
// 1.6有这个线程的cell
//wasUncontended=true,更新失败,cells没有初始化扩容,collide=true,占用cells,扩容完成,重新来
//有这个线程的cell,cas失败,说明2个线程同时更新这个cell,就扩容。既然你不让我加,竞争这么厉害,那么扩容试试看。
else if (cellsBusy == 0 && /* 这期间其他线程可以做很多事 */casCellsBusy()) { //不可能多个线程同时进这里面来。 try {
if (cells == as) { //锁住cells了,最开始as = cells,但是现在as不一定=cells,所以判断cells没变扩容,
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;// 扩容意向为false
continue; // 扩容后还没有设置值(肯定重新来)
}
//1.7 有这个线程的cell
h = advanceProbe(h);// 修改当前线程的hash,降低hash冲突(线程hash改变是无所谓的,关注的是里面的值,与哪个线程放进去无关),避免下次还映射到这个cell。
} // 2。某个线程执行时候,前一个if判断:没有分段更新,cells==null或者cells.length=0。走到这里时候cells有可能不为空了,但是要进入这if必须:cellsBusy=0,
//同时cells还是刚才那个as=null(没有扩容和初始化)并且casCellsBusy()抢成功,就去初始化。
//线程执行到这里:之前判断:【没有cells】,并且现在【没人扩容或者初始化,并且cells为空】就初始化。
else if (cellsBusy == 0 && cells == as && /*这期间其他线程可以做很多事*/casCellsBusy()) {// cellsBusy=1,别的线程就不能动cells //不可能多个线程同时进这里面来。 boolean init = false;
try {
if (cells == as) { //锁住cells了,但是cells不一定=as=空或者null了, 锁住之后一定要再检测一次,如果还是null就初始化
Cell[] rs = new Cell[2];// 初始化时只创建两个单元
rs[h & 1] = new Cell(x);// 对其中一个单元进行累积操作,另一个不管,继续为null
cells = rs;
init = true;
}
} finally {
cellsBusy = 0;// 释放锁
}
if (init)// 初始化成功退出,初始化失败继续来
break;
} // 3。走到这里:前面判断不成立(不代表现在的前面判断也不成立),之前判断:cells=null或者cells.length=0【没有cells】并且cellsBusy=1或者 cells被改变了【有人正在初始化或扩容】或者casCellsBusy()失败。
//有了分段更新,还是可以用base,提高效率。准备去扩容的,但是现在有可能别人已经扩容了(cells != as)或者casCellsBusy()失败(抢着去扩容没有抢成功)
else if (casBase(v = base, ((fn == null) ? v + x : fn.applyAsLong(v, x))))
break; // 更新base,成功就退出。
} /*如果Cells表为空,尝试获取锁之后初始化表(初始大小为2);
如果Cells表非空,对应的Cell为空,自旋锁未被占用,尝试获取锁,添加新的Cell;
如果Cells表非空,找到线程对应的Cell,尝试通过CAS更新该值;
如果Cells表非空,线程对应的Cell CAS更新失败,说明存在竞争,尝试获取自旋锁之后扩容,将cells数组扩大,降低每个cell的并发量后再试*/
} // double更long的逻辑基本上是一样的
final void doubleAccumulate(double x, DoubleBinaryOperator fn, boolean wasUncontended) {
int h;
if ((h = getProbe()) == 0) {
ThreadLocalRandom.current(); // force initialization
h = getProbe();
wasUncontended = true;
}
boolean collide = false; // True if last slot nonempty
for (;;) {
Cell[] as;
Cell a;
int n;
long v;
if ((as = cells) != null && (n = as.length) > 0) {
if ((a = as[(n - 1) & h]) == null) {
if (cellsBusy == 0) { // Try to attach new Cell
Cell r = new Cell(Double.doubleToRawLongBits(x));
if (cellsBusy == 0 && casCellsBusy()) {
boolean created = false;
try { // Recheck under lock
Cell[] rs;
int m, j;
if ((rs = cells) != null && (m = rs.length) > 0 && rs[j = (m - 1) & h] == null) {
rs[j] = r;
created = true;
}
} finally {
cellsBusy = 0;
}
if (created)
break;
continue; // Slot is now non-empty
}
}
collide = false;
} else if (!wasUncontended) // CAS already known to fail
wasUncontended = true; // Continue after rehash
else if (a.cas(v = a.value, ((fn == null) ? Double.doubleToRawLongBits(Double.longBitsToDouble(v) + x)
: Double.doubleToRawLongBits(fn.applyAsDouble(Double.longBitsToDouble(v), x)))))
break;
else if (n >= NCPU || cells != as)
collide = false; // At max size or stale
else if (!collide)
collide = true;
else if (cellsBusy == 0 && casCellsBusy()) {
try {
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;
continue; // Retry with expanded table
}
h = advanceProbe(h);
} else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
boolean init = false;
try { // Initialize table
if (cells == as) {
Cell[] rs = new Cell[2];
rs[h & 1] = new Cell(Double.doubleToRawLongBits(x));
cells = rs;
init = true;
}
} finally {
cellsBusy = 0;
}
if (init)
break;
} else if (casBase(v = base, ((fn == null) ? Double.doubleToRawLongBits(Double.longBitsToDouble(v) + x)
: Double.doubleToRawLongBits(fn.applyAsDouble(Double.longBitsToDouble(v), x)))))
break; // Fall back on using base
}
} private static final sun.misc.Unsafe UNSAFE;
private static final long BASE;
private static final long CELLSBUSY;
private static final long PROBE;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> sk = Striped641.class;
BASE = UNSAFE.objectFieldOffset(sk.getDeclaredField("base"));
CELLSBUSY = UNSAFE.objectFieldOffset(sk.getDeclaredField("cellsBusy"));
Class<?> tk = Thread.class;
PROBE = UNSAFE.objectFieldOffset(tk.getDeclaredField("threadLocalRandomProbe"));
} catch (Exception e) {
throw new Error(e);
}
} // 一个Cell里面一个value,可以看成是一个简化的AtomicLong,通过cas操作来更新value的值
// @sun.misc.Contended是一个高端的注解,代表使用缓存行填来避免伪共享
@sun.misc.Contended
static final class Cell {
volatile long value;// cas更新其值
Cell(long x) {
value = x;
}
final boolean cas(long cmp, long val) {// cas更新
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);
}
}
} }
public class LongAdder1 extends Striped641 implements Serializable {
private static final long serialVersionUID = 7249069246863182397L; public LongAdder1() {
} /*看到这里我想应该有很多人明白为什么LongAdder会比AtomicLong更高效了,
没错,唯一会制约AtomicLong高效的原因是高并发,高并发意味着CAS的失败几率更高,
重试次数更多,越多线程重试,CAS失败几率又越高,变成恶性循环,AtomicLong效率降低。
那怎么解决?** LongAdder给了我们一个非常容易想到的解决方案:减少并发,将单一value的更新压力分担到多个value中去
(每个线程更新value数组里面的自己value【多个线程访问的同一个数组(也只有一个数组)但是cas更新的是只是其中一个value】
【因为数组有限,所以不同的线程也会出现同时cas更新一个value的情况】【会出现:多个线程同时访问这个数组的同一个cell】,
不再是多个线程更新同一个value导致cas经常失败), 降低单个value的 “热度”,分段更新 */
/*这样,线程数再多也会分担到多个value上去更新,只需要增加value就可以降低 value的 “热度”
AtomicLong中的 恶性循环不就解决了吗? cells 就是这个 “段” cell中的value 就是存放更新值的,
这样,当我需要总数时,把cells 中的value都累加一下不就可以了么!!*/
/*当然,聪明之处远远不仅仅这里,在看看add方法中的代码,casBase方法可不可以不要,直接分段更新,上来就计算 索引位置,然后更新value?
答案是不好,不是不行,因为,casBase操作等价于AtomicLong中的CAS操作,要知道,LongAdder这样的处理方式是有坏处的,
分段操作必然带来空间上的浪费,可以空间换时间,但是,能不换就不换,看空间时间都节约~!
所以,casBase操作保证了在低并发时,不会立即进入分支做分段更新操作,因为低并发时,
casBase操作基本都会成功,只有并发高到一定程度了,才会进入分支,
所以,Doug Lea对该类的说明是:** 低并发时LongAdder和AtomicLong性能差不多,高并发时LongAdder更高效!***/
/*因为低并发时候,使用的是base的原子更新,没有启用分段更新(cells=null,并且casBase成功),高并发才启用分段更新。*/
/*如此,longAccumulate中做了什么事,也基本略知一二了,因为cell中的value都更新失败(说明该索引到这个cell的线程也很多
,并发也很高时) 或者cells数组为空时才会调用longAccumulate,*/ // +x,并发计数器LongAdder加X。要么在base+x更新要么在Cell[]数组里面找到对应的Cell+x更新。
public void add(long x) {//base和cells只有一个,并且是LongAdder的属性。
Cell[] as;
long b, v;
int m;
Cell a;
//cells!=null不用判断后面进去(表明已经启用了分段更新),cells=null并且base的cas更新失败进去(表示没有启用分段更新但是高并发了,
//需要启用分段更新),cells=null并且base的cas更新成功就退出(没有启用分段更新,并且不是高并发,此时跟AotomicLong是一样的)。
//并发时候更新失败,AtomicLong的处理方式是死循环尝试更新,直到成功才返回,而LongAdder则是进入这个分支。
if ((as = cells) != null || !casBase(b = base, b + x)/*cas把base的值从b变成b+x*/) {
//进来:1.已经启用分段更新了。2.没有启用分段更新但是cas失败了表示高并发了。否则:没有启用分段更新并且不是高并发,就不进来。
boolean uncontended = true;
if (as == null //cells=null进去,没有启用分段更新(进来了)表示高并发了。
|| (m = as.length - 1) < 0 //cells.length<=0,没有启用分段更新(进来了)表示高并发了。
|| (a = as[getProbe() & m]) == null //对as的长度取余,从as中获取这个线程对应的a Cell。=null表示还没有这个线程对应的cell,
|| !(uncontended = a.cas(v = a.value, v + x))) //a这个Cell里面的value增加x失败, 更新成功就不会进下面了。 //1.cells=null。2.cells!=null但没有这个线程的Cell。2.有这个线程的Cell但是更新失败了。
longAccumulate(x, null, uncontended); //uncontended=false表示更新失败了,=true表示没有这个线程的Cell(不可能是更新成功了,更新成功就进不来这里)。
}
} public void increment() {
add(1L);
} public void decrement() {
add(-1L);
} //将多个cell数组中的值加起来的和就类似于AtomicLong中的value
// 此返回值可能不是绝对准确的,因为调用这个方法时还有其他线程可能正在进行计数累加,
// 方法的返回时刻和调用时刻不是同一个点,在有并发的情况下,这个值只是近似准确的计数值
// 高并发时,除非全局加锁,否则得不到程序运行中某个时刻绝对准确的值,但是全局加锁在高并发情况下是下下策
// 在很多的并发场景中,计数操作并不是核心,这种情况下允许计数器的值出现一点偏差,此时可以使用LongAdder
// 在必须依赖准确计数值的场景中,应该自己处理而不是使用通用的类。
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 reset() {
Cell[] as = cells;
Cell a;
base = 0L;
if (as != null) {
for (int i = 0; i < as.length; ++i) {
if ((a = as[i]) != null)
a.value = 0L;
}
}
} public long sumThenReset() {
Cell[] as = cells;
Cell a;
long sum = base;
base = 0L;
if (as != null) {
for (int i = 0; i < as.length; ++i) {
if ((a = as[i]) != null) {
sum += a.value;
a.value = 0L;
}
}
}
return sum;
} public String toString() {
return Long.toString(sum());
} public long longValue() {
return sum();
} public int intValue() {
return (int) sum();
} public float floatValue() {
return (float) sum();
} public double doubleValue() {
return (double) sum();
} private static class SerializationProxy implements Serializable {
private static final long serialVersionUID = 7249069246863182397L; private final long value;//LongAdder1的总和 SerializationProxy(LongAdder1 a) {
value = a.sum();
} private Object readResolve() {
LongAdder1 a = new LongAdder1();
a.base = value;
return a;
}
} private Object writeReplace() {
return new SerializationProxy(this);
} private void readObject(java.io.ObjectInputStream s) throws java.io.InvalidObjectException {
throw new java.io.InvalidObjectException("Proxy required");
} }
public abstract class Number1 implements java.io.Serializable { public abstract int intValue(); public abstract long longValue(); public abstract float floatValue(); public abstract double doubleValue(); /*
System.out.println((byte)127);//127
System.out.println((byte)128);//-128
System.out.println((byte)129);//-127
System.out.println((byte)255);//-1
System.out.println((byte)256);//0
System.out.println((byte)257);//1
*/
public byte byteValue() {
return (byte) intValue();
} public short shortValue() {
return (short) intValue();
} private static final long serialVersionUID = -8742448824652078965L;
}
LongAdder源码分析的更多相关文章
- 死磕 java并发包之LongAdder源码分析
问题 (1)java8中为什么要新增LongAdder? (2)LongAdder的实现方式? (3)LongAdder与AtomicLong的对比? 简介 LongAdder是java8中新增的原子 ...
- LongAdder 源码分析
LongAdder LongAdder 能解决什么问题?什么时候使用 LongAdder? 1)LongAdder 内部包含一个基础值[base]和一个单元[Cell]数组. 没有竞争的情况下,要累加 ...
- 死磕 java集合之ConcurrentHashMap源码分析(三)
本章接着上两章,链接直达: 死磕 java集合之ConcurrentHashMap源码分析(一) 死磕 java集合之ConcurrentHashMap源码分析(二) 删除元素 删除元素跟添加元素一样 ...
- JDK源码分析(12)之 ConcurrentHashMap 详解
本文将主要讲述 JDK1.8 版本 的 ConcurrentHashMap,其内部结构和很多的哈希优化算法,都是和 JDK1.8 版本的 HashMap是一样的,所以在阅读本文之前,一定要先了解 Ha ...
- ConcurrentHashMap JDK 1.6 源码分析
前言 前段时间把 JDK 1.6 中的 HashMap 主要的一些操作源码分析了一次.既然把 HashMap 源码分析了, 就顺便把 JDK 1.6 中 ConcurrentHashMap 的主要一些 ...
- 并发-ConcurrentHashMap源码分析
ConcurrentHashMap 参考: http://www.cnblogs.com/chengxiao/p/6842045.html https://my.oschina.net/hosee/b ...
- 2. Sentinel源码分析—Sentinel是如何进行流量统计的?
这一篇我还是继续上一篇没有讲完的内容,先上一个例子: private static final int threadCount = 100; public static void main(Strin ...
- 源码分析 Alibaba sentinel 滑动窗口实现原理(文末附原理图)
要实现限流.熔断等功能,首先要解决的问题是如何实时采集服务(资源)调用信息.例如将某一个接口设置的限流阔值 1W/tps,那首先如何判断当前的 TPS 是多少?Alibaba Sentinel 采用滑 ...
- ABP源码分析一:整体项目结构及目录
ABP是一套非常优秀的web应用程序架构,适合用来搭建集中式架构的web应用程序. 整个Abp的Infrastructure是以Abp这个package为核心模块(core)+15个模块(module ...
随机推荐
- CLOS : Common Lisp 的面向对象支持
1. defclass ( :accessor/reader/writer ; :initarg ; :initform 2. defgeneric 3. defmethod ----- ...
- 3-awk
1.输出双引号:awk '{print "\""}' #放大:awk '{print " \" "}'使用“”双引号把一个 ...
- CSP复赛day2模拟题
没错,我又爆零了.....先让我自闭一分钟.....so 当你忘记努力的时候,现实会用一记响亮的耳光告诉你东西南北在哪. 好了,现在重归正题: 全国信息学奥林匹克联赛(NOIP2014) 复赛模拟题 ...
- H5新增form控件和表单属性
第一个知识点:表单的属性及总结 第二个知识点:H5新增的表单控件和属性以及总结 第一个知识点: 我们常见的表单验证有哪些呢 text 文本框标签 password 密码框 checkbox 多选框 r ...
- Android源码分析(十五)----GPS冷启动实现原理分析
一:原理分析 主要sendExtraCommand方法中传递两个参数, 根据如下源码可以知道第一个参数传递delete_aiding_data,第二个参数传递null即可. @Override pub ...
- Class.forName() 与 ClassLoader.loadClass()的区别
看到一个面试题,说说Class.forName() 与 ClassLoader.loadClass()的区别,特意记录一下,方便后续查阅. 在我们写java代码时,通常用这两种方式来动 ...
- h5表单亲测
Document 下载进度: 标签. 牛奶 面包 男 女 one two three 按钮 搜索 请输入搜索内容 加密强度 用户名 Email 密码 年龄 身高 生日 这一系列是很酷的一个类型,完全解 ...
- MySQL数据物理备份之lvm快照
使用lvm快照实现物理备份 优点: 几乎是热备(创建快照前把表上锁,创建完后立即释放) 支持所有存储引擎 备份速度快 无需使用昂贵的商业软件(它是操作系统级别的) 缺点: 可能需要跨部门协调(使用操作 ...
- 一探istio-proxy(envoy)容器里的秘密
今天,在测试环境跑通了istio. 惭愧,是用MicroK8s跑的,其它环境,不敢呀. 基本功能都OK了. 在运行了istio-injection=enabled之后,每个pod运行时,会多一个ist ...
- 使用WIFI网卡的AP功能
前几篇博客中,wifi无线网卡都工作于STA模式,那么它能否工作于AP模式.本篇博客就研究使wifi 无线网卡工作于AP模式.使用一个应用程序hostapd,关于它的介绍可以去此网站https://w ...