赞
踩
AtomicLong提供了原子性操作long类型数据的解决方案。
我们直到在Java中:
1 byte = 8 bit
1 字节 = 8位
在32位操作系统中,8字节(即:64位)的long和double 变量由于会被JVM当作两个分离的32位来进行操作,所以不具有原子性。而使用AtomicLong能让long的操作保持原子型。
@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性能低下的原因所在。
@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
LongAdder类是JDK1.8新增的一个原子性操作类。
高并发下N多线程同时去操作一个变量会造成大量线程CAS失败,然后处于自旋状态,导致严重浪费CPU资源,降低了并发性。既然AtomicLong性能问题是由于过多线程同时去竞争同一个变量的更新而降低的,那么如果把一个变量分解为多个变量,让同样多的线程去竞争多个资源。
LongAdder则是内部维护一个Cells数组,每个Cell里面有一个初始值为0的long型变量,在同等并发量的情况下,争夺单个变量的线程会减少,这是变相的减少了争夺共享资源的并发量,另外多个线程在争夺同一个原子变量时候,如果失败并不是自旋CAS重试,而是尝试获取其他原子变量的锁,最后当获取当前值时候是把所有变量的值累加后再加上base的值返回的。
另外由于Cells占用内存是相对比较大的,所以一开始并不创建,而是在需要时候再创建,也就是惰性加载,当一开始没有空间时候,所有的更新都是操作base变量。
在 LongAdder 的父类 Striped64 中存在一个 volatile Cell[] cells; 数组,其长度是2 的幂次方,每个Cell都使用 @Contended 注解进行修饰,而@Contended注解可以进行缓存行填充,从而解决伪共享问题。伪共享会导致缓存行失效,缓存一致性开销变大。
value是long类型的占据8个字节,而左边填充七个,右边填充七个, value值在正中间
而一个缓存行是64个字节,所以一个value一定会占用一个缓存行。
伪共享指的是多个线程同时读写同一个缓存行的不同变量时导致的 CPU缓存失效。尽管这些变量之间没有任何关系,但由于在主内存中邻近,存在于同一个缓存行之中,它们的相互覆盖会导致频繁的缓存未命中,引发性能下降。
@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
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的用时短,效率高。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。