我们来模拟50个线程,每个线程对计数器递增100万次,最终结果应该是5000万。

我们使用4种方式实现,看一下其性能,然后引出为什么需要使用LongAdderLongAccumulator

1、方式一:synchronized 方式实现

public class MakeTheCounter {static int count = 0;public static synchronized void incr() {count++;}public static void main(String[] args) throws ExecutionException, InterruptedException {for (int i = 0; i < 10; i++) {count = 0;m1();}}private static void m1() throws InterruptedException {long t1 = System.currentTimeMillis();int threadCount = 50;CountDownLatch countDownLatch = new CountDownLatch(threadCount);for (int i = 0; i < threadCount; i++) {new Thread(() -> {try {for (int j = 0; j < 1000000; j++) {incr();}} finally {countDownLatch.countDown();}}).start();}countDownLatch.await();long t2 = System.currentTimeMillis();System.out.printf("结果:%s,耗时(ms):%s%n", count, (t2 - t1));}}

运行上面的代码,输出结果:

结果:50000000,耗时(ms):1238
结果:50000000,耗时(ms):743
结果:50000000,耗时(ms):513
结果:50000000,耗时(ms):512
结果:50000000,耗时(ms):466
结果:50000000,耗时(ms):478
结果:50000000,耗时(ms):478
结果:50000000,耗时(ms):458
结果:50000000,耗时(ms):485
结果:50000000,耗时(ms):473

平均耗时:588毫秒

2、方式2:AtomicLong实现

public class MakeTheCounter {static AtomicLong count = new AtomicLong(0);public static void incr() {count.incrementAndGet();}public static void main(String[] args) throws ExecutionException, InterruptedException {for (int i = 0; i < 10; i++) {count.set(0);m1();}}private static void m1() throws InterruptedException {long t1 = System.currentTimeMillis();int threadCount = 50;CountDownLatch countDownLatch = new CountDownLatch(threadCount);for (int i = 0; i < threadCount; i++) {new Thread(() -> {try {for (int j = 0; j < 1000000; j++) {incr();}} finally {countDownLatch.countDown();}}).start();}countDownLatch.await();long t2 = System.currentTimeMillis();System.out.println(String.format("结果:%s,耗时(ms):%s", count, (t2 - t1)));}}

运行上面的代码,输出结果:

结果:50000000,耗时(ms):992
结果:50000000,耗时(ms):959
结果:50000000,耗时(ms):796
结果:50000000,耗时(ms):1019
结果:50000000,耗时(ms):967
结果:50000000,耗时(ms):1067
结果:50000000,耗时(ms):787
结果:50000000,耗时(ms):933
结果:50000000,耗时(ms):911
结果:50000000,耗时(ms):978

平均耗时:940毫秒

AtomicLong内部采用CAS的方式实现,并发量大的情况下,CAS失败率比较高,导致性能比synchronized还低一些。并发量不是太大的情况下,CAS性能还是可以的。

3、方式3:LongAdder实现

先介绍一下LongAdder,说到LongAdder,不得不提的就是AtomicLong,AtomicLong是JDK1.5开始出现的,里面主要使用了一个long类型的value作为成员变量,然后使用循环的CAS操作去操作value的值,并发量比较大的情况下,CAS操作失败的概率较高,内部失败了会重试,导致耗时可能会增加。

LongAdder是JDK1.8开始出现的,所提供的API基本上可以替换掉原先的AtomicLong

LongAdder在并发量比较大的情况下,操作数据的时候,相当于把这个数字分成了很多份数字,然后交给多个人去管控,每个管控者负责保证部分数字在多线程情况下操作的正确性。当多线程访问的时,通过hash算法映射到具体管控者去操作数据,最后再汇总所有的管控者的数据,得到最终结果。相当于降低了并发情况下锁的粒度,所以效率比较高,看一下下面的图,方便理解:

代码:

public class MakeTheCounter {static LongAdder count = new LongAdder();public static void incr() {count.increment();}public static void main(String[] args) throws ExecutionException, InterruptedException {for (int i = 0; i < 10; i++) {count.reset();m1();}}private static void m1() throws ExecutionException, InterruptedException {long t1 = System.currentTimeMillis();int threadCount = 50;CountDownLatch countDownLatch = new CountDownLatch(threadCount);for (int i = 0; i < threadCount; i++) {new Thread(() -> {try {for (int j = 0; j < 1000000; j++) {incr();}} finally {countDownLatch.countDown();}}).start();}countDownLatch.await();long t2 = System.currentTimeMillis();System.out.println(String.format("结果:%s,耗时(ms):%s", count.sum(), (t2 - t1)));}}

运行上面的代码,输出结果:

结果:50000000,耗时(ms):362
结果:50000000,耗时(ms):525
结果:50000000,耗时(ms):410
结果:50000000,耗时(ms):370
结果:50000000,耗时(ms):228
结果:50000000,耗时(ms):263
结果:50000000,耗时(ms):302
结果:50000000,耗时(ms):220
结果:50000000,耗时(ms):218
结果:50000000,耗时(ms):219

平均耗时:311毫秒

代码中new LongAdder()创建一个LongAdder对象,内部数字初始值是0,调用increment()方法可以对LongAdder内部的值原子递增1。reset()方法可以重置LongAdder的值,使其归0。

4、方式4:LongAccumulator实现

  • LongAccumulator介绍

LongAccumulator是LongAdder的功能增强版。LongAdder的API只有对数值的加减,而LongAccumulator提供了自定义的函数操作,其构造函数如下:

/*** accumulatorFunction:需要执行的二元函数(接收2个long作为形参,并返回1个long)* identity:初始值*/
public LongAccumulator(LongBinaryOperator accumulatorFunction,long identity) {this.function = accumulatorFunction;base = this.identity = identity;
}

示例代码:

public class MakeTheCounter {static LongAccumulator count = new LongAccumulator((x, y) -> x + y, 0L);public static void incr() {count.accumulate(1);}public static void main(String[] args) throws ExecutionException, InterruptedException {for (int i = 0; i < 10; i++) {count.reset();m1();}}private static void m1() throws ExecutionException, InterruptedException {long t1 = System.currentTimeMillis();int threadCount = 50;CountDownLatch countDownLatch = new CountDownLatch(threadCount);for (int i = 0; i < threadCount; i++) {new Thread(() -> {try {for (int j = 0; j < 1000000; j++) {incr();}} finally {countDownLatch.countDown();}}).start();}countDownLatch.await();long t2 = System.currentTimeMillis();System.out.println(String.format("结果:%s,耗时(ms):%s", count.longValue(), (t2 - t1)));}}

运行上面的代码,输出结果:

结果:50000000,耗时(ms):248
结果:50000000,耗时(ms):217
结果:50000000,耗时(ms):251
结果:50000000,耗时(ms):246
结果:50000000,耗时(ms):302
结果:50000000,耗时(ms):309
结果:50000000,耗时(ms):317
结果:50000000,耗时(ms):235
结果:50000000,耗时(ms):224
结果:50000000,耗时(ms):217

平均耗时:256毫秒

LongAccumulator的效率和LongAdder差不多,不过更灵活一些。

调用new LongAdder()等价于new LongAccumulator((x, y) -> x + y, 0L)

从上面4个示例的结果来看,LongAdder、LongAccumulator全面超越同步锁及AtomicLong的方式,建议在使用AtomicLong的地方可以直接替换为LongAdder、LongAccumulator,吞吐量更高一些。

文章参考:http://www.itsoku.com/ 博主觉得这个文章的内容挺不错的,感兴趣的可以去了解一下。

JUC学习 - 原子操作增强类LongAdder、LongAccumulator相关推荐

  1. Java Review - 并发编程_原子操作类LongAdder LongAccumulator剖析

    文章目录 概述 小Demo 源码分析 重要的方法 long sum() reset sumThenReset longValue() add(long x) longAccumulate(long x ...

  2. 专访微软邓力:语音识别与非监督深度学习、增强学习、词嵌入、类脑智能

    在俞栋接受CSDN专访解读基于深度学习的语音识别技术及CNTK开源软件的同时,<解析深度学习-语音识别实践>一书的另一位作者.微软人工智能首席科学家邓力也接受CSDN专访,以另外的视角诠释 ...

  3. spring学习笔记(14)引介增强详解:定时器实例:无侵入式动态增强类功能

    引介增强实例需求 在前面我们已经提到了前置.后置.环绕.最终.异常等增强形式,它们的增强对象都是针对方法级别的,而引介增强,则是对类级别的增强,我们可以通过引介增强为目标类添加新的属性和方法,更为诱人 ...

  4. Java JUC学习 - ConcurrentLinkedDeque 详解

    Java JUC学习 - ConcurrentLinkedDeque 详解 0x00 前言 如何实现并发程序,对于Java以及其他高级语言来说都是一件并不容易的事情.在大一上学期的时候,我们学习了链表 ...

  5. JUC学习笔记(二)

    JUC学习笔记(二) volatile关键字 JMM(Java Memory Model | Java内存模型) JMM同步规定 原理 工作流程 内存模型图 volatile特点 可见性 禁止指令重排 ...

  6. 【尚硅谷/周阳】JUC学习笔记

    JUC学习笔记[尚硅谷/周阳] 本文章基于B站视频教程[juc 与 jvm 并发编程 Java 必学_阳哥- 尚硅谷]进行整理记录,仅用于个人学习,交流使用. 目录标题 JUC学习笔记[尚硅谷/周阳] ...

  7. C#中关于增强类功能的几种方式

    C#中关于增强类功能的几种方式 本文主要讲解如何利用C#语言自身的特性来对一个类的功能进行丰富与增强,便于拓展现有项目的一些功能. 拓展方法 扩展方法被定义为静态方法,通过实例方法语法进行调用.方法的 ...

  8. 学习C++ 丨 类(Classes)的定义与实现!C/C++必学知识点!

    一."类" 的介绍 在C++中, 用 "类" 来描述 "对象", 所谓的"对象"是指现实世界中的一切事物.那么类就可以看 ...

  9. SpringMVC 学习-异常处理 SimpleMappingExceptionResolver 类

    SpringMVC 学习-异常处理 SimpleMappingExceptionResolver 类 参考文章: (1)SpringMVC 学习-异常处理 SimpleMappingException ...

  10. 学习Kotlin(三)类和接口

    推荐阅读: 学习Kotlin(一)为什么使用Kotlin 学习Kotlin(二)基本语法 学习Kotlin(三)类和接口 学习Kotlin(四)对象与泛型 学习Kotlin(五)函数与Lambda表达 ...

最新文章

  1. hadoop2.7.3+spark2.1.0+scala2.12.1环境搭建(3)http://www.cnblogs.com/liugh/p/6624491.html
  2. 访问数,每次访问页数,平均停留时间,跳出率
  3. Python面试必须要看的15个问题
  4. C#解密出生日期【C#】
  5. 学习Spring Boot:(二)启动原理
  6. 29 CO配置-控制-产品成本控制-成本对象控制-期末结算-定义分配
  7. 例子:选择一个联系人
  8. 分治法解决组合问题(递归)
  9. VISUAL STUDIO调试器指南---断点和跟踪点
  10. 双屏、3屏拼接——A卡、N卡——Windows、Linux
  11. netgen.5.0.0下载地址与Windows下编译方法
  12. 生活记录:压抑暂时解脱
  13. Godaddy注册的域名转发、转向教程
  14. 固态硬盘之主控哪家强?
  15. cross_val_score的 scoring参数值解析
  16. 解决部署Hexo后出现的The custom domain for your GitHub Pages site is pointed at an outdated IP address警告
  17. 抓取百万数据,我们深扒了风口上的OYO酒店
  18. 基于阿克曼转向的车辆运动学模型
  19. HD-SDI芯片方案选择及其应用与发展方向
  20. Golang(1)-简介及特性

热门文章

  1. Android专业DJ,著名音乐游戏《DJ英雄》登陆Android Market
  2. C# WPF – 利用“Attached Property” 把 RoutedEvent 接上 ICommand
  3. APM的解锁(ARM)流程
  4. java 正数转负数_Java程序将正整数转换为负数,并将负整数转换为正数
  5. 关于 C++ 打印 PDF 打印及 PDF 转图片、合并
  6. 鼠标滚轮失灵上下乱窜的简单修理办法
  7. python语言标识符首字符不能是汉字_python二级电子教案 第2章 Python语言基本语法元素...
  8. 起底硅谷最神秘、估值最高的大数据公司:Palantir
  9. 实施不良资产证券化 信用评级不能少
  10. 省钱兄霸王餐源码uniapp前端