LongAdder和LongAccumulator用于大数据统计,且不要求实时精确。
LongAdder的基本思路就是分散热点,将value值分散到一个Cell数组中,不同线程会命中到数组的不同槽中,各个线程只对自己槽中的那个值进行CAS操作,这样热点就被分散了,冲突概率就小很多。如果要获取真正的long值,只要将各个槽中的变量值累加返回(空间换时间)
例子
class Demo{
public volatile int i = 0;
public AtomicInteger atomicInteger = new AtomicInteger();
public LongAdder longAdder = new LongAdder();
LongAccumulator longAccumulator = new LongAccumulator((x, y) -> x+y,0);
public synchronized void syc_add(){
i++;
}
public void syc_in_add(){
synchronized (this){
i++;
}
}
public void atomic (){
atomicInteger.incrementAndGet();
}
public void add(){
longAdder.increment();
}
public void add_LongAccumulator() {
longAccumulator.accumulate(1);
}
}
public class LongAdderDemo {
public static final int SIZE_THREAD = 50;
public static final int _1W = 10000;
public static void main(String[] args) throws InterruptedException {
Demo demo = new Demo();
long startTime;
long endTime;
CountDownLatch countDownLatch1 = new CountDownLatch(SIZE_THREAD);
CountDownLatch countDownLatch2 = new CountDownLatch(SIZE_THREAD);
CountDownLatch countDownLatch3 = new CountDownLatch(SIZE_THREAD);
CountDownLatch countDownLatch4 = new CountDownLatch(SIZE_THREAD);
CountDownLatch countDownLatch5 = new CountDownLatch(SIZE_THREAD);
startTime = System.currentTimeMillis();
for (int i = 1; i <=SIZE_THREAD; i++) {
new Thread(() -> {
try
{
for (int j = 1; j <=100 * _1W; j++) {
demo.syc_add();
}
}catch (Exception e){
e.printStackTrace();
}finally {
countDownLatch1.countDown();
}
},String.valueOf(i)).start();
}
countDownLatch1.await();
endTime = System.currentTimeMillis();
System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t syc_add()"+"\t"+demo.i);
demo.i=0;
startTime = System.currentTimeMillis();
for (int i = 1; i <=SIZE_THREAD; i++) {
new Thread(() -> {
try
{
for (int j = 1; j <=100 * _1W; j++) {
demo.syc_in_add();
}
}catch (Exception e){
e.printStackTrace();
}finally {
countDownLatch2.countDown();
}
},String.valueOf(i)).start();
}
countDownLatch2.await();
endTime = System.currentTimeMillis();
System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t syc_in_add()"+"\t"+demo.i);
startTime = System.currentTimeMillis();
for (int i = 1; i <=SIZE_THREAD; i++) {
new Thread(() -> {
try
{
for (int j = 1; j <=100 * _1W; j++) {
demo.atomic();
}
}catch (Exception e){
e.printStackTrace();
}finally {
countDownLatch3.countDown();
}
},String.valueOf(i)).start();
}
countDownLatch3.await();
endTime = System.currentTimeMillis();
System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t atomic()"+"\t"+demo.atomicInteger.get());
startTime = System.currentTimeMillis();
for (int i = 1; i <=SIZE_THREAD; i++) {
new Thread(() -> {
try
{
for (int j = 1; j <=100 * _1W; j++) {
demo.add_LongAccumulator();
}
}catch (Exception e){
e.printStackTrace();
}finally {
countDownLatch4.countDown();
}
},String.valueOf(i)).start();
}
countDownLatch4.await();
endTime = System.currentTimeMillis();
System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t add_LongAccumulator()"+"\t"+demo.longAccumulator.get());
startTime = System.currentTimeMillis();
for (int i = 1; i <=SIZE_THREAD; i++) {
new Thread(() -> {
try
{
for (int j = 1; j <=100 * _1W; j++) {
demo.add();
}
}catch (Exception e){
e.printStackTrace();
}finally {
countDownLatch5.countDown();
}
},String.valueOf(i)).start();
}
countDownLatch5.await();
endTime = System.currentTimeMillis();
System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t add()"+"\t"+demo.longAdder.sum());
}
}
源码分析
多个线程需要同时对value进行操作时候,可以对线程id进行hash得到hash值,在根据hash值映射到这个数组cells的某个下标,再对该下标所对应的值进行自增操作,当所有线程操作完成,将数组cells的所有值和base值都加起来。
Value = Base + Cell[1]+...+Cell[N]
继承图
Striped64(重要的属性)
-
base:类似于AtomciLong中的全局value值,没有竞争情况下数据直接累加到base上,或者cells扩容时,也需要将数据写入base上。
-
collide:表示扩容意向,false一定不会扩容,true可能会扩容。
-
cellsBusy:初始化cells或者扩容cells需要获取锁,0:表示无锁状态 1:表示其他线程已经持有锁
-
caseCellsBusy():通过CAS操作修改cellsBusy的值,CAS成功代表获取锁,返回true
-
NCPU:当前计算机CPU数量,Cell数组扩容时会使用到
-
getProbe():获取当前线程hash值
-
advanceProbe():重置当前线程的hash值
add方法
-
as表示cells引用
-
b表示获取的base值
-
v表示期待值
-
m表示cells数组的长度
-
a表示当前线程命中的cell单元格
-
(cs = cells) != null 判断cells是否为null
-
casBase(b = base, b + x) 基于base使用cas是否更新成功
-
(m = cs.length - 1) < 0 判断cells数量是否小于零
-
cs == null 判断cell数组是否为null
-
(c = cs[getProbe() & m]) == null 计算线程id的hash值判断落点cell是否为null
-
(uncontended = c.cas(v = c.value, v + x) 判断是否需要扩容
longAccumulate方法
case1:Cell[]已经初始化
case2:Cell[]数组未初始化
case3:Cell[]数组正在初始化中
case2
case1
判断当前线程hash后指向的数据位置元素是否为空,如果为空则将Cell数据放入数组中,跳出循环,如果不空则继续循环。
case3
在Base上进行操作
sum方法
遍历Cell[]进行累加,实现最终一致性而不是强一致性。