目录
- 基本类型原子类
- 数组类型原子类
- 引用类型原子类
- 对象的属性修改原子类
- 原子操作增强类
- LongAdder 高性能原理说明
- LongAdder源码深度解析
- LongAdder小总结
- 相关文献
分组来给大家讲解相关原子类的常用api使用,不会全部都讲完,只是抽取几个比较经典的讲一下案例使用
基本类型原子类
- AtomicInteger
- AtomicBoolean
- AtomicLong
问题案例:为什么结果是不准呢,因为里面的50个线程还没有跑完,main线程去拿结果,就会导致没有计算完拿到了错误结果
要等前面50个线程跑完,让main线程sleep一段时间也可以,但是不太好,怎么解决呢?
countDownLatch,50个线程,等到50个线程都跑完,后面的线程拿到最终值
数组类型原子类
- AtomicIntegerArray
- AtomicLongArray
- AtomicReferenceArray
案例演示:数组根据下标获取数值
引用类型原子类
- AtomicReference
- AtomicStampedReference(利用版本号,解决ABA问题,能够知道修改了几次)
- AtomicMarkableReference(将状态戳简化为true/false,解决是否修改过)
案例演示:同时间不同线程拿到的都是false初始化值,但是a线程执行完之后,发生了改变,b线程再去修改时,发现值被使用过,再次修改失败
对象的属性修改原子类
- AtomicLongFieldUpdater
- AtomicIntegerFieldUpdater
- AtomicReferenceFieldUpdater
作用:以一种线程安全的方式操作非线程安全对象内的某些字段
不用原子操作类以前:效果虽然能实现,但是加了synchronized
现在不加锁,针对引用类的字段进行原子性操作:效果也一致
上面是针对integer,那么引用类中的字段呢?来,让我们接着奏乐接着舞
AtomicReferenceFieldUpdater:基于反射的实用程序,可以对指定类的指定volatile引用字段进行原子更新。
需求:多线程并发调用一个类的初始化方法,如果未被初始化,将执行初始化工作,要求只能被初始化一次,只有一个线程操作成功
案例实现如下:
原子操作增强类
- DoubleAccumulator
- DoubleAdder
- LongAccumulator
- LongAdder
这几个是java8出来的增强类,前面十二个是java5的原子操作类
按照以上图片红框内容,我们来实现一个热点商品点赞数,点赞累加统计,要求吞吐量极高,不要求实时精准的(98%)需求(因为正在统计时,也在实时增加点赞数)
单线程简单使用:
以上案例可以总结出:LongAdder支持简单的加法计算,计算初始量从0开始,而LongAccumulator支持自定义计算公式,且初始值也可以自定义
多线程案例:50个线程,每个线程100w次,统计总点赞数
内部类:
class ClickNumber{int number = 0;public synchronized void clickSynchronized(){number++;}AtomicLong atomicLong = new AtomicLong(0);public void clickAtomicLong(){atomicLong.incrementAndGet();}LongAdder longAdder = new LongAdder();public void clickLongAdder(){longAdder.increment();}LongAccumulator longAccumulator = new LongAccumulator((x,y)->x+y,0);public void clickLongAccumulator(){longAccumulator.accumulate(1);}
}
实现类:
public static final int _1w = 10000;public static final int threadNumber = 50;public static void main(String[] args) throws InterruptedException {ClickNumber clickNumber = new ClickNumber();long startTime;long endTime;final CountDownLatch countDownLatch1 = new CountDownLatch(threadNumber);final CountDownLatch countDownLatch2 = new CountDownLatch(threadNumber);final CountDownLatch countDownLatch3 = new CountDownLatch(threadNumber);final CountDownLatch countDownLatch4 = new CountDownLatch(threadNumber);startTime = System.currentTimeMillis();for (int i = 0; i < threadNumber; i++) {new Thread(()->{try {for (int j = 1; j <= 100 * _1w; j++) {clickNumber.clickSynchronized();}} finally {countDownLatch1.countDown();}},String.valueOf(i)).start();}countDownLatch1.await();endTime = System.currentTimeMillis();System.out.println("耗时:"+(endTime-startTime)+"毫秒 \t clickSynchronized:"+clickNumber.number);startTime = System.currentTimeMillis();for (int i = 0; i < threadNumber; i++) {new Thread(()->{try {for (int j = 1; j <= 100 * _1w; j++) {clickNumber.clickAtomicLong();}} finally {countDownLatch2.countDown();}},String.valueOf(i)).start();}countDownLatch2.await();endTime = System.currentTimeMillis();System.out.println("耗时:"+(endTime-startTime)+"毫秒 \t clickAtomicLong:"+clickNumber.atomicLong.get());startTime = System.currentTimeMillis();for (int i = 0; i < threadNumber; i++) {new Thread(()->{try {for (int j = 1; j <= 100 * _1w; j++) {clickNumber.clickLongAdder();}} finally {countDownLatch3.countDown();}},String.valueOf(i)).start();}countDownLatch3.await();endTime = System.currentTimeMillis();System.out.println("耗时:"+(endTime-startTime)+"毫秒 \t clickLongAdder:"+clickNumber.longAdder.sum());startTime = System.currentTimeMillis();for (int i = 0; i < threadNumber; i++) {new Thread(()->{try {for (int j = 1; j <= 100 * _1w; j++) {clickNumber.clickLongAccumulator();}} finally {countDownLatch4.countDown();}},String.valueOf(i)).start();}countDownLatch4.await();endTime = System.currentTimeMillis();System.out.println("耗时:"+(endTime-startTime)+"毫秒 \t clickLongAccumulator:"+clickNumber.longAccumulator.get());}
效果图:
从效果图中看,LongAdder高并发大数据量下性能比AtomicLong好10倍
那么为什么LongAdder能那么快呢?
LongAdder 高性能原理说明
AtomicLong是通过CAS自旋保证原子性,高并发大数量的情况下,也是一个个执行,所以性能不佳
LongAdder底层有base跟cell理念设计,在低并发情况下,跟AtomicLong性质一样,通过本身base的CAS自旋处理,但是高并发情况下,base一个肯定忙不过来,就启用cell扩容,开启多个窗口cell[0]、cell[1]、cell[…],利用base+cell达到分散热点的作用,如果要获取真正的long值,结果就是base+cell数组
而以上LongAdder设计思想是通过Striped64这个类落地实现的,LongAdder是Striped64的子类
LongAdder性能那么好主要原因是Striped64,这是Striped64类的一些变量或参数定义,其中最重要的就是NCPU、cells、base、cellsBusy
- NCPU:当前计算机CPU数量,即cells数组的最大长度,Cell数组扩容时会使用到
- cells:cell数组,为二次方的扩容,2、4、8…
- base:类似于AtomicLong中全局的value值。在没有竟争情况下数据直接累加到base上,或者cells扩容时,也需要将数据写入到base上
- cellsBusy:初始化cells或者扩容cells需要获取锁,0:表示无锁状态 1:表示其他线程已经持有了锁
- collide:表示扩容意向,false一定不会扩容,true可能会扩容
- casCelsBusy():通过CAS操作修改cellsBusy的值,CAS成功代表获取锁,返回true
- getRrobe():获取当前线程的hash值
- advanceProbe():重置当前线程的hash值
LongAdder源码深度解析
源码分析按照以下链路来看,其中longAccumulate是重点
LongAdder.increment()->
LongAdder.add()->
Striped64.longAccumulate()->
LongAdder.sum()
。。。。。此节点未完结,后面继续补坑,小编被同事背刺了,很无语
LongAdder小总结
AtomicLong:线程安全,可允许一些性能损耗,要求高精度时可使用,保证精度,性能代价;是多个线程针对单个热点值value进行原子操作
原理:CAS+自旋(incrementAndGet)
场景:低并发下的全局计算,Atomicong能保证并发情况下计数的准确性,其内部通过CAS来解决并发安全性的问题。
缺陷:高并发后性能急剧下降,为什么?因为AtomicLong的自旋会成为瓶颈,高并发后造成大量cpu空转
LongAdder:当需要在高并发下有较好的性能表现,且对值的精确度要求不高时,可以使用,保证性能,用精度付出代价;是每个线程拥有自己的槽,各个线程一般只对自己槽中的那个值进行CAS操作
原理:CAS+Base+Cell数组分散,空间换时间并分散了热点数据
场景:高并发下的全局计算
缺陷:sum求和后还有计算线程修改结果的话,最后结果不够准确
相关文献
jdk api文档:https://www.runoob.com/manual/jdk11api/java.base/java/util/concurrent/atomic/package-summary.html
就先说到这 \color{#008B8B}{ 就先说到这} 就先说到这
在下 A p o l l o \color{#008B8B}{在下Apollo} 在下Apollo
一个爱分享 J a v a 、生活的小人物, \color{#008B8B}{一个爱分享Java、生活的小人物,} 一个爱分享Java、生活的小人物,
咱们来日方长,有缘江湖再见,告辞! \color{#008B8B}{咱们来日方长,有缘江湖再见,告辞!} 咱们来日方长,有缘江湖再见,告辞!