基于JDK 7,我们如何实现一个多线程计数器?一般做法是定义一个volatile long或定义一个AtomicLong(底层也是volatile long),然后在每个线程中用CAS操作对它进行add操作。这两种做法都是没问题的,功能是正确的、性能也还好,我们继续按照原来的方式使用即可。不过,如果你的项目升级到了JDK 8,还可以进一步提高多线程计数器的性能,让它比传统的volatile long方式更高效。JDK 8新增的LongAdder类(父类是Striped64,包含一些公用方法)就是用来完成这个目的的,使用方法就像下面这样的。

LongAdder使用起来跟AtomicLong类似且非常简单。那么LongAdder与AtomicLong有什么不同点,它是如何提高性能的?

1. 消除热点:把一个变量拆成多个变量

AtomicLong之所以有性能瓶颈,是因为当有非常多的线程并发地对其执行CAS操作时,会产生大量竞争(竞争的含义:多个线程同时写入某个变量的时候,只有一个能成功)。那些竞争失败的线程需要重新读出volatile long的最新值,然后把自己的增量加上去,再用CAS操作与其他线程竞争。所有线程都需要通过这个AtomicLong,在经过这个AtomicLong的时候是串行执行的。虽然写入一个变量的代价很低,但是终归是瓶颈。

多个线程都去读写同一个volatile long,让这个long成为了性能瓶颈。因此,LongAdder把这个中心化的long拆成多个long从而减少竞争。需要总和的时候,再把这些被拆开的long求和加起来。这样导致增加了内存空间的占用量,相当于是在用空间换取时间,如图所示。

2. 动态扩容:根据负载情况增加拆分变量

但是应该怎么拆分以及拆成多少个呢?显然,不应该拆成固定个数的long,因为这样比较死板,而是应该根据访问LongAdder的线程的竞争情况,拆分成特定个数的long,即动态拆分策略。当线程竞争不激烈的时候,让LongAdder中只存在一个volatile long;当竞争变激烈后,让LongAdder中多增加一些volatile long;但是并非volatile long越多越好,当数量增加到CPU个数之后,再增加拆分变量已没有多大意义(因为此时不存在两个线程同时写一个volatile long的情况)。

以上思路对应到LongAdder的具体实现(在其父类Striped64中)就是原来的volatile long被拆成一个volatile long base 和一个Cell cells[],如下图所示。

Cell本质上就是一个volatile long,只不过它的定义增加了一个@sun.misc.Contended注解。这个注解的作用是减少,原子变量因为存放在相邻的位置,容易导致CPU的cache line冲突而削弱性能的问题(所以,如果需要使用原子变量数组,记得仿照Striped64的Cell类,否则性能将很差)。其中,cells是一个长度为2的n次幂的数组,每次扩容都变为原来大小的2倍,当大小超过CPU个数时不再增长(长度超过cpu个数后已经没有意义了,因为同一时刻最多只有N=cpu个数的线程同时运行)。

3. 将线程hash到cells数组slot

把一个long拆成一个base和多个cell后,add操作如何计数?首先尝试写入base,如果写入操作一直没有遇到竞争(即用CAS操作修改base全都成功),那么修改的都是base,cells为null;当遇到第一次竞争时(CAS操作修改base失败),cells被初始化为一个长度等于2的cells数组,并且把当前线程映射到cells数组的一个slot,然后再在这个位于这个slot的Cell上执行CAS操作;如果在Cell上的CAS操作也失败了,则把当前线程的probe值向前调整(probe得作用相当于线程的hash值),再次尝试映射并且执行CAS;如果这里的CAS失败了,那么视为hash冲突并且执行扩容,然后再回头重试。这里的具体执行逻辑比较繁琐,如果不是特别感兴趣建议不必深究。

这里根据线程的probe值(作用相当于hashcode)找到Cells[]数组某个slot的思想跟HashMap/ConcurrentHashMap完全一样,即:

  1. 保证数组长度length等于2的n次方(例如8,二进制是10000000)
  2. 然后有一个掩码mask=length-1(二进制01111111),
  3. 用mask与hash码执行index = mask & hash,结果刚好是数组下标。

这样就找到了slot。这里管理cells[]和映射线程的功能正是Striped64所提供的功能,这个类的名字“Striped64”曾经困扰我。现在明白了其含义了:JDK源码术语dynamic striping指把一个变量拆成多个,放在一个可扩展的数组中管理,Striped64指的是数组中的变量是64位。

通过以上优化手段,LongAdder在并发访问量非常高的情况下,性能显著优于AtomicLong。虽然LongAdder的并发写入性能高,但它的读操作并不是原子操作,无法得到某个时刻的精确值。例如,在调用sum()的时候,如果还有线程正在写入,那么sum()返回的就不是当时的精确值,使用的时候需要留意。

总之,LongAdder适用于统计和显示系统中的某些高速变化的状态,是Java高级工程师应该掌握的API。

Java高级程序员必备:高性能计数器及Striped64和LongAdder相关推荐

  1. Java高级程序猿必备的全套超详细面试题和答案

    我不知道今年还有没有金三银四了,但是不管怎样我们学习的脚步不能停滞,越是艰难的时候,我们越要坚持学习提高自己,所以不管是自己学习还是面试,我相信你读完下面的系列文章,一定会更上一层楼. 面试题和答案全 ...

  2. 524页《Java中高级程序员必备核心知识》总结,令人犹如醍醐灌顶

    说在前面 知乎上有个很热门的话题:中国的程序员数量是否已经饱和或者过剩? 今年大家都有一个共同的感受:工作不好找,面试越来越难. 其实,造成这种现象不仅是因为今年受疫情影响,倒闭了很多公司,很多公司缩 ...

  3. 总结《Java中高级程序员必备核心知识》,令人犹如醍醐灌顶

    说在前面 知乎上有个很热门的话题:中国的程序员数量是否已经饱和或者过剩? 今年大家都有一个共同的感受:工作不好找,面试越来越难. 其实,造成这种现象不仅是因为今年受疫情影响,倒闭了很多公司,很多公司缩 ...

  4. Java高级程序员(5年左右)面试的题目集

    Java高级程序员(5年左右)面试的题目集 https://blog.csdn.net/fangqun663775/article/details/73614850?utm_source=blogxg ...

  5. 为什么3年的Java高级程序员薪水仅仅8k-10k,而一个Linux底层C语言程序员两年经验就敢要1...

    为什么80%的码农都做不了架构师?>>>    为什么3年的Java高级程序员薪水仅仅8k-10k,而一个Linux底层C语言程序员两年经验就敢要10k的薪水?   由于目前国内嵌入 ...

  6. 如何才能成为java高级程序员?

    身为程序员,一旦进入技术行列,就开启了持续学习的道路,更迭迅速的互联网时代,技术自然也是一代一代的更新,在技术进阶的道路上,要不断吸收新的想法和技术知识. 牛逼的人总是让人羡慕,但如何才能让自己成为牛 ...

  7. 做为一名java高级程序员,需要了解哪些岗位?

    一.Java高级程序员 要想成为JAVA(高级)程序员也称Java高级工程师,肯定要学习JAVA.一般的程序员或许只需知道一些JAVA的语法结构就可以应付了.但要成为JAVA高级程序员,您要对JAVA ...

  8. java高级程序员(Java高级程序员招聘)

    Java高级软件工程师和Java程序员有什么区别 现实中的java高级软件工程师就是在某一个行业有很充足的开发经验,很了解开发模式就差不多了,一般的公司都会叫你高级工程师 程序员就是只敲敲代码 ,对啥 ...

  9. 怎样才能成为java高级程序员?应该具备的技能有哪些?(一)

    众所周知我国的软件编程行业是非常捞金的一个行业,在当下社会成为一名高级程序员是所有人都羡慕的,那怎么才能成为一名高级程序员呢?一名高级程序员应该具备哪些技能呢? 一.JAVA. 要想成为JAVA(高级 ...

最新文章

  1. Go开发者路线图2019,请收下这份指南
  2. Markdown与 $\LaTeX$ 公式的使用入门指南
  3. angular 模板
  4. CSS3 skew倾斜、rotate旋转动画
  5. 2.3 《计算机组成原理》之浮点数的表示(基本格式、规格化[左规右规]、表示范围、IEEE754标准详解)
  6. 华为服务器故障灯不开机_华为服务器日常维护及故障处理介绍V.ppt
  7. javafx 表单_JavaFX 2:创建登录表单
  8. 人脸检测与识别的趋势和分析
  9. android 库编译报错,Android Studio编译项目报错
  10. 动态规划——乘积为正数的最长子数组长度(Leetcode 1567)
  11. LeetCode刷题(2)
  12. 100.于电脑右下角的小喇叭不见了的解决
  13. 二维随机变量期望公式_多维随机变量的特征数
  14. 双网卡电脑同时访问内外网设置静态路由表
  15. 利用Echarts+阿里云地图选择器绘制可交互的行政区划地图
  16. 比方便面还方便~利用Python开发一个桌面小程序
  17. CH340国产USB转异步串口芯片替代CP2102对比CH340C与CH340G
  18. 将图像平移到画布中心python_python前端之Photoshop
  19. assoc 和 ftype
  20. 学java的就业方向_JAVA的就业方向是什么?

热门文章

  1. PB 数据窗口数据导入Excel, 如果存在则追加,不存在则创建。
  2. WIN10下TensorFlow GPU版安装顺序
  3. html关于布局的说法错误的是,关于可迁移技能以下说法错误的是
  4. HW算法题:判断字符串子序列
  5. 手机链游撼动腾讯王者荣耀?Nova Battles更具潜力
  6. 老程序员教你如何提高开发效率、成为大神1——人文思维进化与信众
  7. 解决Aria2 BT下载速度慢没速度的问题
  8. Pythont打开 txt 格式的文件
  9. 舒适区、学习区、恐慌区
  10. 反斜杠“\”与斜杠“/” 的区别