要说这篇博客只能算个简单说明吧,我个人对于看源码其实并不是很有心得,只是因为上了年纪,二十又四,在此记录一下,怕是过后忘记罢了。
LongAdder,在研究并发时看到了这个类,jdk8的新类,我对于jdk8的新特性还没完全体验完,jdk10都出来了,好像是可以写var这种弱类型引用了吧,变化真是太快了,不多说废话了,来说今天的主角。
它号称比Atomic更高效,那么就一起看看源码吧,建议打开ide,然后搜索这个类的源码来一起看,ide有注释,有括号的匹配等,看起来要方便的多。比看我这大段代码强很多。在看源码之前,对这种包含设计思想的类呢,个人喜欢查找一下它的一个设计思路(不知道这么说对不对,就是如何高效实现的过程吧),下面这张图是我在简书网站上截取的图片:
我觉得不难理解,脑子里有了这个概念之后再来看源码就相对好一点了,毕竟我了解了实现思路,脑子里会有一个印象,对出现的操作,变量的含义就不是那么难以理解了。贴一下源码:
上面截取这一部分,可以看到,有一个不带参数的构造器,本身继承自Striped64 这个类
挑截图上的add方法来讲吧,大家打开源码也可以看到,其他方法都是会调用它。
首先是一下变量的初始化,Cell[] as cell是啥??点进去看。
很简短的一个class,它是Striped64的一个内部类,里面有一个value属性。并且有个cas方法,里面就是调用了unsafe的方法,与Atomic操作一样。
回到add方法,进入if判断,看一下这几个判断条件,拿第一来说,cells是否==null,找一下cells,点击可以发现是在Striped64中,没有初始化它的操作,只有在函数中的,说明开始就是null的;casbase函数,点进去看看还是与Atomic一样的cas方法,不过前面的条件满足了,后面的就不会执行了。也就是说第一次执行,if条件的第一条满足了,进入if。
发现进入后还有一个if,第一条==null又是满足了,其他的几个条件:m=as.length-1>0,数组不为空,但是length是<1的,其实这跟空差不多了;(a = as[getProbe() & m]) == null 这个是说cell数组随机取出的一个cell元素是空的,也就是没有初始化过;!(uncontended = a.cas(v = a.value, v + x))这个看上去很明显,做了一次cas操作失败了。
上述条件满足任何一个,都会进入到if,执行longAccumulate(x, null, uncontended);
迫不及待的点进去,这个长代码我不贴了,大家自己的ide看的很清楚,贴上去这么长的代码还有这么多括号,相信自己对括号也是很难,所以不要懒了,动动手指打开一下ide操作一下。
看到有三条路径,那么贴出第一条的代码
这个红线指出的条件之前就说过,这里再重复一遍:cells !=null并且不止是初始化过,还有长度,并且,随机选取的cells数组中的某一个元素是空的,是空的,就是说明没有被用过。
cellsBusy,有个这个变量,从字面翻译过来是cells正在忙,应该是一个操作执行的 ‘加锁’ 变量,==0,可以猜测出,0值代表的是不忙,那1就代表忙了呗,大概是这样吧,这里是猜测,继续看,不忙的话,并且casCellBusy()返回true,就创建了一个cell,并且再次判断了cellsbusy,官方给了个注释:optimistcally create 翻译过来是乐观地创建,应该是乐观锁的一种写法。try-catch中的操作简单来说就是将创建好的cell添加到了cells的数组中。finally,将这个乐观锁标志释放。
上面提到的cascellbusy()这里说一下,里面其实很简单,调用了unsafe的方法,去改变这个标记量,可以理解为加一个锁。
接下来是第二个if,这个简单多了,满足了条件就创建一个cells数组,它就是在这里初始化的,初始默认数组大小是2。
最后一个if代码很少,简单说一下,就是上述条件不满足的话,再进行一次cas更新。
整个流程其实还算简单:大致总结一下,通过cells这个数组,每个字段初始化都是0的,然后提供了add,increasement等方法,变化的数量都是1,这就保证了cells数组的和就是多个线程操作的总和,操作到相同cell,会有cas操作来进行限制,因此在高并发的时候,将原来一个变量,扩充到了多个。效率提高了。
注意:获取值是要配置sum方法使用的。此方法很简单,也没有所得概念在其中,是将cells中的所有元素相加得到总和。
PS:随笔博客,忽略了一下东西,只是挑了其中一个方法大概讲解了一下这个类,还请大家留言批评指正