目录
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. 总结
当需要在高并发下有较好的性能表现,且对值的精确度要求不高时,可以使用。
即当需要保证性能,不要求精度,可以使用。