点击上方“Java精选”,选择“设为星标”

别问别人为什么,多问自己凭什么!

下方留言必回,有问必答!

每天 08:00 更新文章,每天进步一点点...

概述

AtomicLong是作者Doug Lea在jdk1.5版本发布于java.util.concurrent.atomic并发包下的类。而LongAdder是道格·利(Doug Lea的中文名)在java8中发布的类。

有了AtomicLong为何还需要LongAdder?

在这里,就不得不分析一下AtomicLong的缺点了。

先来看一下AtomicLong.incrementAndGet()方法的源码

/*** Atomically increments by one the current value.** @return the updated value*/
public final long incrementAndGet() {return unsafe.getAndAddLong(this, valueOffset, 1L) + 1L;
}

接着跟踪Unsafe类的getAndAddLong方法

图:Unsafe类的getAndAddLong方法

可以清楚地看到,AtomicLong的原子性自增操作,是通过CAS实现的。

在多线程竞争不激烈的情况下,这样做是合适的。但是如果线程竞争激烈,会造成大量线程在原地打转、不停尝试去修改值,但是老是发现值被修改了,于是继续自旋。这样浪费了大量的CPU资源。

而且,由于AtomicLong持有的成员变量value是volatile关键字修饰的,线程修改了临界资源后,需要刷新到其他线程,也是要费一番功夫的。

画个图来理解:

图:volatile刷新共享内存。

而LongAdder也有一个volatile修饰的base值,但是当竞争激烈时,多个线程并不会一直自旋来修改这个值,而是采用了分段的思想。竞争激烈时,各个线程会分散累加到自己所对应的Cell[]数组的某一个数组对象元素中,而不会大家共用一个。

这样做,可以把不同线程对应到不同的Cell中进行修改,降低了对临界资源的竞争。本质上,是用空间换时间。

LongAdder 和 AtomicLong 的性能对比

分析了半天,没有证据,还是不能让人信服的。唯有证明一下,才能心服口服。

接下来,我会创建一个容量为1,000的固定线程池,然后提交100倍于线程池容量的线程,每个线程中,对临界资源进行+1操作。等所有线程执行结束后,统计运行时长,并关闭线程池。临界资源分别使用AtomicLong和LongAdder表示,来对比二者的区别。

首先尝试让每个线程进行100次+1操作,最后的累加结果应该为:线程数×100=1,000×100×100 = 10,000,000

AtomicLongDemo.java

import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicLong;/*** <pre>* 程序目的:演示 AtomicInteger、AtomicLong 在高并发下性能不好* 在16个线程下使用AtomicLong。* 每次值发生变化时,都会刷新回主内存,竞争激烈时,这样的 flush 和 refresh 操作耗费了很多资源,而且 CAS 也会经常失败* </pre>* created at 2020/8/11 06:11* @author lerry*/
public class AtomicLongDemo {/*** 线程池内线程数*/final static int POOL_SIZE = 1000;public static void main(String[] args) throws InterruptedException {long start = System.currentTimeMillis();AtomicLong counter = new AtomicLong(0);ExecutorService service = Executors.newFixedThreadPool(POOL_SIZE);ArrayList<Future> futures = new ArrayList<>(POOL_SIZE);for (int i = 0; i < POOL_SIZE * 100; i++) {futures.add(service.submit(new Task(counter)));}// 等待所有线程执行完for (Future future : futures) {try {future.get();}catch (ExecutionException e) {e.printStackTrace();}}NumberFormat numberFormat = NumberFormat.getInstance();System.out.printf("统计结果为:[%s]\n", numberFormat.format(counter.get()));System.out.printf("耗时:[%d]毫秒", (System.currentTimeMillis() - start));// 关闭线程池service.shutdown();}/*** 有一个 AtomicLong 成员变量,每次执行N次+1操作*/static class Task implements Runnable {private final AtomicLong counter;public Task(AtomicLong counter) {this.counter = counter;}/*** 每个线程执行N次+1操作*/@Overridepublic void run() {for (int i = 0; i < 100; i++) {counter.incrementAndGet();}}// end run}// end class
}

LongAdderDemo.java

import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.LongAdder;/*** <pre>* 程序目的:和 AtomicLong 进行性能对比* </pre>* created at 2020/8/11 06:25* @author lerry*/
public class LongAdderDemo {/*** 线程池内线程数*/final static int POOL_SIZE = 1000;public static void main(String[] args) throws InterruptedException {long start = System.currentTimeMillis();LongAdder counter = new LongAdder();ExecutorService service = Executors.newFixedThreadPool(POOL_SIZE);ArrayList<Future> futures = new ArrayList<>(POOL_SIZE);for (int i = 0; i < POOL_SIZE * 100; i++) {futures.add(service.submit(new LongAdderDemo.Task(counter)));}// 等待所有线程执行完for (Future future : futures) {try {future.get();}catch (ExecutionException e) {e.printStackTrace();}}NumberFormat numberFormat = NumberFormat.getInstance();System.out.printf("统计结果为:[%s]\n", numberFormat.format(counter.sum()));System.out.printf("耗时:[%d]毫秒", (System.currentTimeMillis() - start));// 关闭线程池service.shutdown();}/*** 有一个 LongAdder 成员变量,每次执行N次+1操作*/static class Task implements Runnable {private final LongAdder counter;public Task(LongAdder counter) {this.counter = counter;}/*** 每个线程执行N次+1操作*/@Overridepublic void run() {for (int i = 0; i < 100; i++) {counter.increment();}}// end run}// end class
}

备注:AtomicLong的运行结果截图和LongAdder的运行结果截图放在了一起,AtomicLong的在上、LongAdder的在下。另外,关注Java精选公众号,回复“Java面试”,送你一份在线免费刷面试题宝典!

每个线程进行100次累加的运行结果

图:100次累加的执行结果

可以看到,AtomicLong耗时516毫秒,LongAdder耗时438毫秒,516➗438≈1.18,前者耗时是后者的一倍多一点。区别好像不是很大。

进行1,000次累加呢?

图:1000次累加的执行结果

可以看到,AtomicLong耗时3034毫秒,LongAdder耗时575毫秒,3034➗575≈5.28,前者耗时是后者的5倍多。区别开始变得明显。

进行10,000次累加呢?

图:10,000次累加的执行结果

可以看到,AtomicLong耗时30868毫秒,LongAdder耗时2167毫秒,30868➗2167≈14.24,前者耗时是后者的14倍多。差距变得更大了。

进行50,000次累加呢?

图:50,000次累加的执行结果

可以看到,AtomicLong耗时148375毫秒,LongAdder耗时9754毫秒,148375➗9754≈15.21,前者耗时是后者的15倍多。差距进一步扩大。

结论

在每个线程执行的累加数量变多时,LongAdder比AtomicLong性能优势越发明显。

LongAdder由于采用了分段理念,降低了线程间的竞争冲突,而AtomicLong却因多个线程并行竞争同一个value值,从而影响了性能。

在低竞争的情况下,AtomicLong 和 LongAdder 这两个类具有相似的特征,吞吐量也是相似的,因为竞争不高。

但是在竞争激烈的情况下,LongAdder 的预期吞吐量要高得多,经过试验,LongAdder 的吞吐量大约是 AtomicLong 的十倍,不过凡事总要付出代价,LongAdder 在保证高效的同时,也需要消耗更多的空间。

环境说明

  • java -version

java version "1.8.0_251"
Java(TM) SE Runtime Environment (build 1.8.0_251-b08)
Java HotSpot(TM) 64-Bit Server VM (build 25.251-b08, mixed mode)
  • OS:macOS High Sierra 10.13.4

作者:快乐柠檬

blog.csdn.net/limenghua9112/article/details/107950744

期往精选  点击标题可跳转

【070期】阿里云面试:如何发现 Redis 热点 Key ,解决方案有哪些?

【071期】面试官问:什么场景下适合使用 ArrayList 或 LinkedList?

【072期】面试官:对并发熟悉吗?说说 wait、notify、yield 等线程间的协作

【073期】Spring Boot 项目 @Async 默认线程池导致 OOM 问题如何解决?

【074期】摒弃“机械化”编码,Spring Boot 实现动态增删启停定时任务

【075期】面试官问:Spring 使用了哪些设计模式?举例三种即可

【076期】面试官问:你遇到 Redis 线上连接超时吗?一般如何处理?

【077期】面试官问:谈谈你对 Java 线程安全与不安全的理解和看法?

Java精选面试题 - 小程序,3000+ 道面试题在线刷,最新、最全 Java 面试题!

文章有帮助的话,在看,转发吧!

【078期】java.util.* 并发包下 LongAdder 和 AtomicLong 哪个性能更好,为什么?相关推荐

  1. LongAdder和AtomicLong哪个性能更好,为什么?

    点击关注公众号,实用技术文章及时了解 来源:blog.csdn.net/limenghua9112/ article/details/107950744 概述 AtomicLong是作者Doug Le ...

  2. java.util.regex包下的Pattern和Matcher详解(正则匹配)

    java正则表达式通过java.util.regex包下的Pattern类与Matcher类实现(建议在阅读本文时,打开java API文档,当介绍到哪个方法时,查看java API中的方法说明,效果 ...

  3. java concurrent 框架,java.util.concurrent 包下的 Synchronizer 框架

    看完书 java concurrency in practice 当然是想找点啥好玩的东东玩玩. 当看到了Doug Lee 的论文 << The java.util.concurrent ...

  4. Function接口 – Java8中java.util.function包下的函数式接口

    作者:   Mohamed Sanaulla  译者: 李璟(jlee381344197@gmail.com) 早先我写了一篇<函数式接口>,探讨了Java8中函数式接口的用法.如果你正在 ...

  5. java.util 常见_Java基础知识-java.util.concurrent包下常见类的使用

    一,Condition 一个场景,两个线程数数,同时启动两个线程,线程A数1.2.3,然后线程B数4.5.6,最后线程A数7.8.9,程序结束,这涉及到线程之间的通信. public classCon ...

  6. Java工具包工具类,java.util.concurrent 包下工具类的使用

    CountDownLacth 的使用 常用于监听某些初始化操作,等初始化执行完毕,通知主线程继续执行. 先看示例代码: public class UseCountDownLatch { public ...

  7. java.util.concurrent.*下的常见类你了解多少?

    java.util.concurrent.*是标准库提供的一组工具类,帮我们简化并发编程 1.concurrent下的locks锁 1.1.locks锁下常见的锁 ![在这里插入图片描述](https ...

  8. java 并发包之 LongAdder 源码分析

    前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家.点击跳转到教程. LongAdder是java8中新增的原子类,在多线程环境中,它比AtomicLong性能要高出不少 ...

  9. 阿里为什么推崇java_为什么阿里巴巴 Java 开发手册推荐使用 LongAdder,而不是 volatile?...

    阿里<Java开发手册>最新嵩山版在 8.3 日发布,其中有一段内容引起了老王的注意,内容如下: [参考]volatile 解决多线程内存不可见问题.对于一写多读,是可以解决变量同步问题, ...

最新文章

  1. 分布式系统数据一致性解决方案
  2. 关于开发自我训练课程
  3. 美国农业生产与农产品国际贸易 对话国际农民丰收节贸易会
  4. IDA Pro的patch插件 KeyPatch
  5. 北斗导航 | GPS原理与接收机设计——琉璃剑之剑气回肠
  6. 在DockerHub发布Dubbo Admin镜像
  7. Host Switch Plus结合nginx使用
  8. Halcon算子学习:XLD几种边缘连接函数
  9. JavaScript 表格专题
  10. 开源能带来什么?听听入选2021胡润U30创业领袖榜单的90后怎么说
  11. imp命令导入指定表_【imp】使用imp工具迁移数据时迂回地排除特定表的导入
  12. 红皮书:变量、作用域和内存问题(四)
  13. 计算机软件著作权登记怎么填写,计算机软件著作权登记申请表填写说明
  14. EasyPR中文车牌识别系统开发
  15. PS 2022,PR 2018,AE 2017【百度网盘链接,没套路】
  16. endnote文献顺序编号不对_lbrack;求助rsqb;NoteExpress插入文献文章经修改后序号顺序不对如何修改...
  17. 当我们谈注册中心时我们谈什么
  18. 餐厅点菜c语言程序代码,餐馆点菜系统C语言源代码.pdf
  19. springBoot配置全局异常处理器
  20. Open VAS 漏扫工具的安装

热门文章

  1. iOS开发之iOS10简单适配
  2. 第五十一章 SQL函数 DECODE
  3. 几种解决高并发的处理方式
  4. 大数据在城市规划领域的应用思考
  5. 7-2 凑零钱 (30 分)
  6. php wenjian shangchuan shijian,PHP时间和日期 - osc_nnp3dgfb的个人空间 - OSCHINA - 中文开源技术交流社区...
  7. 51单片机ROM和RAM
  8. 期货开户交易交易风格各异
  9. 程序员之间最丧心病狂的鄙视链——编程语言篇
  10. 关于OLED显示屏的理解