简单对比AtomicLong和LongAdder
一、简介
1.AtomicLong
AtomicLong提供了原子性操作long类型数据的解决方案。
我们直到在Java中:
1 byte = 8 bit
1 字节 = 8位
在32位操作系统中,8字节(即:64位)的long和double 变量由于会被JVM当作两个分离的32位来进行操作,所以不具有原子性。而使用AtomicLong能让long的操作保持原子型。
1.1 AtomicLong源码分析
@Test
public void testL() {
AtomicLong atomicLong = new AtomicLong();
System.out.println(atomicLong.addAndGet(30L));
}
运行结果:
其中addAndGet方法源码如下:使用了CAS
compareAndSwapInt(this, stateOffset, expect, update)这个方法的作用就是通过cas技术来预测stateOffset变量的初始值是否是expect,如果是,那么就把stateOffset变量的值变成update,如果不是,那么就一直自旋转,一直到stateOffset变量的初始值是expect,然后在在修改stateOffset变量的值变成update。
缺点:
虽然AtomicLong使用CAS算法,但是CAS失败后还是通过无限循环的自旋锁不断的尝试,这就是高并发下CAS性能低下的原因所在。
1.2 AtomicLong常用方法
@Test
public void testL() {
AtomicLong atomicLong = new AtomicLong();
System.out.println("atomicLong.addAndGet(30L):" + atomicLong.addAndGet(30L));
System.out.println("atomicLong.get():" + atomicLong.get());
AtomicLong atomicLong2 = new AtomicLong(20);
System.out.println("atomicLong2.get():" + atomicLong2.get());
AtomicLong atomicLong3 = new AtomicLong(10);
System.out.println("atomicLong3.addAndGet():" + atomicLong3.addAndGet(2));
AtomicLong atomicLong4 = new AtomicLong(40);
System.out.println("atomicLong4.getAndAdd():" + atomicLong3.getAndAdd(2));
//5、如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。
AtomicLong atomicLong5 = new AtomicLong(10);
atomicLong5.compareAndSet(10, 15);
System.out.println("atomicLong5 compareAndSet:" + atomicLong5.get());
AtomicLong atomicLong6 = new AtomicLong(60);
System.out.println("atomicLong6.getAndIncrement():" + atomicLong6.getAndIncrement());
AtomicLong atomicLong7 = new AtomicLong(70);
System.out.println("atomicLong7.incrementAndGet():" + atomicLong7.incrementAndGet());
AtomicLong atomicLong8 = new AtomicLong(80);
System.out.println("atomicLong8.incrementAndGet():" + atomicLong8.getAndSet(80));
}
结果:
atomicLong.addAndGet(30L):30
atomicLong.get():30
atomicLong2.get():20
atomicLong3.addAndGet():12
atomicLong4.getAndAdd():12
atomicLong5 compareAndSet:15
atomicLong6.getAndIncrement():60
atomicLong7.incrementAndGet():71
atomicLong8.incrementAndGet():80
2.LongAdder
LongAdder类是JDK1.8新增的一个原子性操作类。
高并发下N多线程同时去操作一个变量会造成大量线程CAS失败,然后处于自旋状态,导致严重浪费CPU资源,降低了并发性。既然AtomicLong性能问题是由于过多线程同时去竞争同一个变量的更新而降低的,那么如果把一个变量分解为多个变量,让同样多的线程去竞争多个资源。
LongAdder则是内部维护一个Cells数组,每个Cell里面有一个初始值为0的long型变量,在同等并发量的情况下,争夺单个变量的线程会减少,这是变相的减少了争夺共享资源的并发量,另外多个线程在争夺同一个原子变量时候,如果失败并不是自旋CAS重试,而是尝试获取其他原子变量的锁,最后当获取当前值时候是把所有变量的值累加后再加上base的值返回的。
另外由于Cells占用内存是相对比较大的,所以一开始并不创建,而是在需要时候再创建,也就是惰性加载,当一开始没有空间时候,所有的更新都是操作base变量。
2.1 LongAdder源码分析
在 LongAdder 的父类 Striped64 中存在一个 volatile Cell[] cells; 数组,其长度是2 的幂次方,每个Cell都使用 @Contended 注解进行修饰,而@Contended注解可以进行缓存行填充,从而解决伪共享问题。伪共享会导致缓存行失效,缓存一致性开销变大。
value是long类型的占据8个字节,而左边填充七个,右边填充七个, value值在正中间
而一个缓存行是64个字节,所以一个value一定会占用一个缓存行。
伪共享指的是多个线程同时读写同一个缓存行的不同变量时导致的 CPU缓存失效。尽管这些变量之间没有任何关系,但由于在主内存中邻近,存在于同一个缓存行之中,它们的相互覆盖会导致频繁的缓存未命中,引发性能下降。
2.2 LongAdder常用方法
@Test
public void testLong() {
LongAdder longAdder = new LongAdder();
longAdder.increment();
System.out.println("longAdder:" + longAdder);
LongAdder longAdder2 = new LongAdder();
longAdder2.add(3L);
System.out.println("longAdder2:" + longAdder2);
LongAdder longAdder3 = new LongAdder();
for (int i = 0; i < 10; i++) {
longAdder3.increment();
}
longAdder3.sum();
System.out.println("longAdder3:" + longAdder3);
LongAdder longAdder4 = new LongAdder();
longAdder4.add(5L);
longAdder4.reset();
System.out.println("longAdder4:" + longAdder4);
}
输出结果
longAdder:1
longAdder2:3
longAdder3:10
longAdder4:0
二、AtomicLong和LongAdder对比
LongAddr与AtomicLong的区别
区别 | AtomicLong | LongAdder |
---|---|---|
原理 | 依靠底层的cas来保障原子性的更新数据 | 热点数据分离为cell数组, 每个数组各自维护自身的值 |
高并发(线程竞争激励) | √(将单点的更新压力分散到各个节点,提高性能) | |
线程竞争很低 | √ (简单、高效) | |
缺点 | 线程竞争激烈时,失败概率很高,性能低 | 在统计的时候如果有并发更新, 可能导致统计的数据有误差 |
@SpringBootTest
class CommentApplicationTests {
@Test
public void testAtomicLong() throws InterruptedException {
AtomicLong atomicLong = new AtomicLong();
ArrayList<Thread> list = new ArrayList<>();
for (int i = 0; i < 9999; i++) {
list.add(new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 100000; j++) {
atomicLong.incrementAndGet();
}
}
}));
}
for (Thread thread : list) {
thread.start();
}
for (Thread thread : list) {
thread.join();
}
System.out.println("AtomicLong value is :" + atomicLong.get());
}
@Test
public void testLongAdder() throws InterruptedException {
LongAdder longAdder = new LongAdder();
ArrayList<Thread> list = new ArrayList<>();
for (int i = 0; i < 9999; i++) {
list.add(new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 100000; j++) {
longAdder.increment();
}
}
}));
}
for (Thread thread : list) {
thread.start();
}
for (Thread thread : list) {
thread.join();
}
System.out.println("LongAdder value is :" + longAdder.longValue());
}
@Test
public void testThread() throws InterruptedException {
long start = System.currentTimeMillis();
testLongAdder();
long end = System.currentTimeMillis();
System.out.println("LongAdder 用时:" + (end - start) + "毫秒");
long start1 = System.currentTimeMillis();
testAtomicLong();
long end1 = System.currentTimeMillis();
System.out.println("AtomicLong 用时:" + (end1 - start1) + "毫秒");
}
}
运行testThread方法后结果如下:
结论
高并发下LongAdder的用时短,效率高。