目录
1. 引言
2. AtomicInteger的局限性
3. AtomicInteger与LongAdder 的性能差异
4.LongAdder 的结构
LongAddr架构
Striped64中重要的属性
Striped64中一些变量或者方法的定义
Cell类
5. 分散热点的原理
具体流程图
6. 在实际项目中的应用
7. 总结
1. 引言
在这一部分,可以简要介绍并发编程中的挑战,以及为什么传统的累加方式可能在高并发情况下表现不佳。引出LongAddr作为一种解决方案的背景。
LongAddr属于原子操作增强类,在传统的多线程编程中,我们可能会使用 synchronized
关键字或者 ReentrantLock
来保证对共享变量的原子性操作。然而,这些方法在高度并发的场景下可能会引起性能瓶颈。原子类中的AtomicInteger可以解决这个问题,但同时又出现了LongAddr来提供了一种更高效的并发累加方案。
2. AtomicInteger的局限性
-
竞争热点
当多个线程同时竞争修改同一个 AtomicInteger实例时,会发生竞争热点。所有的线程都试图通过 CAS 操作来更新同一变量,这可能导致竞争激烈,降低性能。
3. AtomicInteger与LongAdder 的性能差异
需求:热点商品点赞计算器,点赞数加加统计,不要求实时精确;
代码:使用50个线程,每个线程100W次,总点赞数出来。
class ClickNumber
{int number = 0;public synchronized void add_Synchronized(){number++;}AtomicInteger atomicInteger = new AtomicInteger();public void add_AtomicInteger(){atomicInteger.incrementAndGet();}AtomicLong atomicLong = new AtomicLong();public void add_AtomicLong(){atomicLong.incrementAndGet();}LongAdder longAdder = new LongAdder();public void add_LongAdder(){longAdder.increment();//longAdder.sum();}LongAccumulator longAccumulator = new LongAccumulator((x,y) -> x+y,0);public void add_LongAccumulator(){longAccumulator.accumulate(1);}}/**** 50个线程,每个线程100W次,总点赞数出来*/
public class LongAdderCalcDemo
{public static final int SIZE_THREAD = 50;public static final int _1W = 10000;public static void main(String[] args) throws InterruptedException{ClickNumber clickNumber = new ClickNumber();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++) {clickNumber.add_Synchronized();}}catch (Exception e){e.printStackTrace();}finally {countDownLatch1.countDown();}},String.valueOf(i)).start();}countDownLatch1.await();endTime = System.currentTimeMillis();System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t add_Synchronized"+"\t"+clickNumber.number);startTime = System.currentTimeMillis();for (int i = 1; i <=SIZE_THREAD; i++) {new Thread(() -> {try{for (int j = 1; j <=100 * _1W; j++) {clickNumber.add_AtomicInteger();}}catch (Exception e){e.printStackTrace();}finally {countDownLatch2.countDown();}},String.valueOf(i)).start();}countDownLatch2.await();endTime = System.currentTimeMillis();System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t add_AtomicInteger"+"\t"+clickNumber.atomicInteger.get());startTime = System.currentTimeMillis();for (int i = 1; i <=SIZE_THREAD; i++) {new Thread(() -> {try{for (int j = 1; j <=100 * _1W; j++) {clickNumber.add_AtomicLong();}}catch (Exception e){e.printStackTrace();}finally {countDownLatch3.countDown();}},String.valueOf(i)).start();}countDownLatch3.await();endTime = System.currentTimeMillis();System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t add_AtomicLong"+"\t"+clickNumber.atomicLong.get());startTime = System.currentTimeMillis();for (int i = 1; i <=SIZE_THREAD; i++) {new Thread(() -> {try{for (int j = 1; j <=100 * _1W; j++) {clickNumber.add_LongAdder();}}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_LongAdder"+"\t"+clickNumber.longAdder.longValue());startTime = System.currentTimeMillis();for (int i = 1; i <=SIZE_THREAD; i++) {new Thread(() -> {try{for (int j = 1; j <=100 * _1W; j++) {clickNumber.add_LongAccumulator();}}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_LongAccumulator"+"\t"+clickNumber.longAccumulator.longValue());}
}
----costTime: 895 毫秒 add_Synchronized 50000000
----costTime: 450 毫秒 add_AtomicInteger 50000000
----costTime: 445 毫秒 add_AtomicLong 50000000
----costTime: 41 毫秒 add_LongAdder 50000000
----costTime: 41 毫秒 add_LongAccumulator 50000000
通过结果能看出在duoLongAddr的性能要比synchronized锁和
AtomicInteger要好。
4.LongAdder 的结构
LongAddr架构
从上图可以看出LongAdder是Striped64的子类。
Striped64中重要的属性
/** Number of CPUS, to place bound on table size CPU数量,即cells数组的最大长度 */
static final int NCPU = Runtime.getRuntime().availableProcessors();/*** Table of cells. When non-null, size is a power of 2.
cells数组,为2的幂,2,4,8,16.....,方便以后位运算*/
transient volatile Cell[] cells;/**基础value值,当并发较低时,只累加该值主要用于没有竞争的情况,通过CAS更新。* Base value, used mainly when there is no contention, but also as* a fallback during table initialization races. Updated via CAS.*/
transient volatile long base;/**创建或者扩容Cells数组时使用的自旋锁变量调整单元格大小(扩容),创建单元格时使用的锁。* Spinlock (locked via CAS) used when resizing and/or creating Cells. */
transient volatile int cellsBusy;
Striped64中一些变量或者方法的定义
- base:类似于AtomicLong中全局的value值。在没有竞争情况下数据直接累加到base上,或者cells扩容时,也需要将数据写入到base上
- collide:表示扩容意向,false一定不会扩容,true可能会扩容。
- cellsBusy:初始化cells或者扩容cells需要获取锁,0:表示无锁状态1:表示其他线程已经持有了锁
- casCellsBusy():通过CAS操作修改cellsBusy的值,CAS成功代表获取锁,返回true
- NCPU:当前计算机CPU数量,CelI数组扩容时会使用到. getProbe():获取当前线程的hash值
- advanceProbe():重置当前线程的hash值
Cell类
Cell类是 java.util.concurrent.atomic 下 Striped64 的一个内部类
5. 分散热点的原理
LongAdder的基本思路就是分散热点,将value值分散到一个Cell数组中,不同线程会命中到数组的不同槽中,各个线程只对自己槽中的那个值进行CAS操作,这样热点就被分散了,冲突的概率就小很多。如果要获取真正的long值,只要将各个槽中的变量值累加返回。
sum()会将所有Cell数组中的value和base累加作为返回值,核心的思想就是将之前AtomicLong一个value的更新压力分散到多个value中去,从而降级更新热点。
Value = Base +
总结:内部有一个base变量,一个Cell[]数组。
base变量:非竞态条件下,直接累加到该变量上,Cell[]数组:竞态条件下,累加个各个线程自己的槽Cell[i]中。
具体流程图
6. 在实际项目中的应用
- 热点商品点赞计算器,点赞数加加统计,不要求实时精确。
- 一个很大的List,里面都是int类型,实现加加。
7. 总结
当需要在高并发下有较好的性能表现,且对值的精确度要求不高时,可以使用。
即当需要保证性能,不要求精度,可以使用。