开始接触AtomicLong是因为在看guava的LoadingCache相关代码的时候,LoadingCache的思路其实很简单明了:用模板方式解决逻辑缓存未命中时获取数据的思路,我之前在项目中正好用到了这个思路。言归正传,LongAdder引起我注意的原因有两个:作者是Douglea,他的地位真的很重要。他说这比AtomicLong更有效率。我们知道AtomicLong已经是一个非常好的解决方案。CAS操作用于并发,比较和设置操作在硬件级别执行。非常有效率。因此,我决定研究为什么LongAdder比AtomicLong更高效。首先看一下LongAdder的继承树:继承自Striped64,这个类包装了一些非常重要的内部类和操作。回头见。在正式开始之前,先强调一下,我们知道AtomicLong的实现是里面有一个value变量。多线程并发自增自减时,使用CAS指令在机器指令层面保证并发的原子性。再看看LongAdder的方法:难怪能和AtomicLong相提并论,连API都这么相似。我们随便挑一个API开始分析。这个API可以用,其他API类似。因此,我选择了add的方式。其实其他的API也依赖这个方法。LongAdder包含一个Cell数组。Cell是Striped64的一个内部类。顾名思义,Cell代表一个最小的单位。这个单位有什么用,后面会讲到。先看定义:Cell内部有一个很重要的值变量,它提供了CAS更新其值的方法。回到add方法:在这里,我有一个问题。AtomicLong已经使用了CAS指令,非常高效(相对于各种锁)。如果LongAdder还是用CAS指令更新值,怎么可能比AtomicLong更高效呢?更何况,还有那么多的内部判断!!!这是我开始时最大的问题,所以,我想,有没有比CAS指令更有效的方法?带着这个疑问,继续。***if第一次调用时判断cells数组必须为null。所以,进入casBase方法:原子更新基础没什么好说的。如果更新成功,本地调用开始返回,否则进入分支。什么时候更新失败?没错,并发的时候,好玩的就开始了。AtomicLong的处理方式是死循环尝试更新,直到成功才会返回,而LongAdder则进入这个分支。在分支内部,通过一个Threadlocal变量threadHashCode获取一个HashCode对象。HashCode对象仍然是Striped64类的内部类。看定义:有一个存储非零随机值的代码变量。回到add方法:得到线程相关的HashCode对象后,得到它的code变量,as[(n-1)&h]这句话相当于对h取模,但是相对于取模,因为它相同的操作效率更高。在Cells数组中计算当前线程的HashCode对应的一个索引位置,取出该位置的Cell对象,使用CAS更新其值。当然,如果as为null,更新失败,就会进入retryUpdate方法。看到这里,我想很多人应该明白为什么LongAdder比AtomicLong效率更高了。是的,制约AtomicLong效率的唯一原因就是高并发。高并发意味着CAS有更高的失败概率和更多的重试。线程重试越多,CAS失败的概率就越高,形成恶性循环,AtomicLong效率下降。如何解决?LongAdder给了我们一个很容易想到的解决方案:降低并发,将单个值的更新压力分担给多个值,降低单个值的“热度”,分段更新!!!这样无论有多少个线程,都会在多个值之间共享更新。只是增加值来降低值的“热度”。AtomicLong里面的恶性循环不就解决了吗?细胞就是这个“片段”。单元格中的值用于存储更新后的值。这样,当我需要合计的时候,只要将单元格中的数值相加即可!!当然,聪明远不止于此。看add方法中的代码,能不能跳过casBase方法,分段更新,计算索引位置,然后更新值?答案不好,也不是不可以,因为casBase操作相当于AtomicLong中的CAS操作。要知道LongAdder的处理方式是有害的。分段操作必然会造成空间的浪费,而空间是可以换取时间的。不过,改不了就别改了,省空间省时间~!所以casBase操作保证了并发低的时候不会马上进入分支进行segmentupdate操作,因为并发低的时候casBase操作基本会成功,只有并发足够高才会进入分支。因此,DougLea对这个类的描述是:LongAdder和AtomicLong在低并发时性能差不多,而LongAdder在高并发时更高效!不过,DoungLea还是没那么简单,小聪明还没完……这样,我基本知道了retryUpdate中做了什么,因为cell中的值更新失败(说明indextothiscell线程多,并发高)或者当cells数组为空时,会调用retryUpdate。所以retryUpdate应该有两件事:扩容,扩充cells数组,降低每个cell的并发度。同样,这也意味着cells数组的rehash动作。将新的Cell数组分配给空的cells变量。是这样吗?继续看代码:代码比较长,转成文字看,为了方便大家看ifelse分支,我把对应的{}标上了相同的颜色。可以看到,这个时候DougLea才愿意使用死循环保证更新成功~!booleancollide=false;//Trueiflastslotnonemptyfor(;;){Cell[]as;Cella;intn;longv;if((as=cells)!=null&&(n=as.length)>0){//分支1if((a=as[(n-1)&h])==null){if(busy==0){//TrytoattachnewCellCellr=newCell(x);//乐观创建if(busy==0&&casBusy()){booleancreated=false;try{//RecheckunderlockCell[]rs;intm,j;if((rs=cells)!=null&&(m=rs.length)>0&&rs[j=(m-1)&h]==null){rs[j]=r;创建=真;}}终于{忙=0;}如果(创建)中断;continue;//Slotisnownon-empty}}collide=false;}elseif(!wasUncontended)//CASalreadyknowntofailwasUncontended=true;//Continueafterrehashelseif(a.cas(v=a.value,fn(v,x)))break;elseif(n>=NCPU||cells!=as)collide=false;//Atmaxsizeorstaleelseif(!collide)collide=true;elseif(busy==0&&casBusy()){try{if(cells==as){//ExpandtableunlessstaleCell[]rs=newC埃尔[n<<1];for(inti=0;i
