JUC包中的分而治之策略-为提高性能而生

一、前言

本次分享我们来共同探讨JUC包中一些有意思的类,包含AtomicLong & LongAdder,ThreadLocalRandom原理。

二、AtomicLong & LongAdder

2.1 AtomicLong 类

AtomicLong是JUC包提供的原子性操作类,其内部通过CAS保证了对计数的原子性更新操作。

大家可以翻看源码发现内部是通过UnSafe(rt.jar)这个类的CAs操作来保证对内部的计数器变量 long value进行原子性更新的,比如JDK8中:

    public final long incrementAndGet() {return unsafe.getAndAddLong(this, valueOffset, 1L) + 1L;}

其中unsafe.getAndAddLong的代码如下:

  public final long getAndAddLong(Object paramObject, long paramLong1, long paramLong2){long l;do{l = getLongVolatile(paramObject, paramLong1);//(1)} while (!compareAndSwapLong(paramObject, paramLong1, l, l + paramLong2));//(2)return l;}

可知最终调用的是native 方法compareAndSwapLong原子性操作。

当多个线程调用同一个AtomicLong实例的incrementAndGet方法后,多个线程都会执行到unsafe.getAndAddLong方法,然后多个线程都会执行到代码(1)处获取计数器的值,然后都会去执行代码(2),如果多个线程同时执行了代码(2),由于CAS具有原子性,所以只有一个线程会更新成功,然后返回true从而退出循环,整个更新操作就OK了。其他线程则CAS失败返回false,则循环一次在次从(1)处获取当前计数器的值,然后在尝试执行(2),这叫做CAS的自旋操作,本质是使用Cpu 资源换取使用锁带来的上下文切换等开销。

2.2 LongAdder类

AtomicLong类为开发人员使用线程安全的计数器提供了方便,但是AtomicLong在高并发下存在一些问题,如上所述,当大量线程调用同一个AtomicLong的实例的方法时候,同时只有一个线程会CAS计数器的值成功,失败的线程则会原地占用cpu进行自旋转重试,这回造成大量线程白白浪费cpu原地自旋转。

在JDK8中新增了一个LongAdder类,其采用分而治之的策略来减少同一个变量的并发竞争度,LongAdder的核心思想是把一个原子变量分解为多个变量,让同样多的线程去竞争多个资源,这样竞争每个资源的线程数就被分担了下来,下面通过图形来理解下两者设计的不同之处:

如上图AtomicLong是多个线程同时竞争同一个原子变量。

如上图LongAdder内部维护多个Cell变量,在同等并发量的情况下,争夺单个变量更新操作的线程量会减少,这是变相的减少了争夺共享资源的并发量。

下面我们首先看下Cell的结构:

    @sun.misc.Contended static final class Cell {volatile long value;Cell(long x) { value = x; }final boolean cas(long cmp, long val) {return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val);}// Unsafe 机制private static final sun.misc.Unsafe UNSAFE;private static final long valueOffset;static {try {UNSAFE = sun.misc.Unsafe.getUnsafe();Class<?> ak = Cell.class;valueOffset = UNSAFE.objectFieldOffset(ak.getDeclaredField("value"));} catch (Exception e) {throw new Error(e);}}}

LongAdder维护了一个延迟初始化的原子性更新数组(默认情况下Cell数组是null)和一个基值变量base,由于Cells占用内存是相对比较大的,所以一开始并不创建,而是在需要时候在创建,也就是惰性 创建。

当一开始判断cell数组是null并且并发线程较少时候所有的累加操作都是对base变量进行的,这时候就退化为了AtomicLong。cell数组的大小保持是2的N次方大小,初始化时候Cell数组的中Cell的元素个数为2,数组里面的变量实体是Cell类型。

当多个线程在争夺同一个Cell原子变量时候如果失败并不是在当前cell变量上一直自旋CAS重试,而是会尝试在其它Cell的变量上进行CAS尝试,这个改变增加了当前线程重试时候CAS成功的可能性。最后获取LongAdder当前值的时候是把所有Cell变量的value值累加后在加上base返回的,如下代码:

    public long sum() {Cell[] as = cells; Cell a;long sum = base;if (as != null) {for (int i = 0; i < as.length; ++i) {if ((a = as[i]) != null)sum += a.value;}}return sum;}

如上代码可知首先把base的值赋值给sum变量,然后通过循环把每个cell元素的value值累加到sum变量上,最后返回sum.

其实这是一种分而治之的策略,先把并发量分担到多个原子变量上,让多个线程并发的对不同的原子变量进行操作,然后获取计数时候在把所有原子变量的计数和累加。

思考问题:

  • 何时初始化cell数组
  • 当前线程如何选择cell中的元素进行访问
  • 如果保证cell中元素更新的线程安全
  • cell数组何时进行扩容,cell元素个数可以无限扩张?

性能对比,这里有一个文章 http://blog.palominolabs.com/2014/02/10/java-8-performance-improvements-longadder-vs-atomiclong/

三、 Random & ThreadLocalRandom

3.1 Random类原理及其局限性

在JDK7之前包括现在java.util.Random应该是使用比较广泛的随机数生成工具类,下面先通过简单的代码看看java.util.Random是如何使用的:

public class RandomTest {public static void main(String[] args) {//(1)创建一个默认种子的随机数生成器Random random = new Random();//(2)输出10个在0-5(包含0,不包含5)之间的随机数for (int i = 0; i < 10; ++i) {System.out.println(random.nextInt(5));}}
}
  • 代码(1)创建一个默认随机数生成器,使用默认的种子。
  • 代码(2)输出输出10个在0-5(包含0,不包含5)之间的随机数。
    public int nextInt(int bound) {//(3)参数检查if (bound <= 0)throw new IllegalArgumentException(BadBound);//(4)根据老的种子生成新的种子int r = next(31);//(5)根据新的种子计算随机数...return r;} 

如上代码可知新的随机数的生成需要两个步骤

  • 首先需要根据老的种子计算生成新的种子。
  • 然后根据新的种子和bound变量通过一定的算法来计算新的随机数。

下面看下next()代码:

    protected int next(int bits) {long oldseed, nextseed;AtomicLong seed = this.seed;do {//(6)获取当前原子变量种子的值oldseed = seed.get();//(7)根据当前种子值计算新的种子nextseed = (oldseed * multiplier + addend) & mask;//(8)使用新种子替换老的种子} while (!seed.compareAndSet(oldseed, nextseed));//(9)return (int)(nextseed >>> (48 - bits));}
  • 代码(6)使用原子变量的get方法获取当前原子变量种子的值
  • 代码(7)根据具体的算法使用当前种子值计算新的种子
  • 代码(8)使用CAS操作,使用新的种子去更新老的种子,多线程下可能多个线程都同时执行到了代码(6)那么可能多个线程都拿到的当前种子的值是同一个,然后执行步骤(7)计算的新种子也都是一样的,但是步骤(8)的CAS操作会保证只有一个线程可以更新老的种子为新的,失败的线程会通过循环从新获取更新后的种子作为当前种子去计算老的种子,这就保证了随机数的随机性。
  • 代码(9)则使用固定算法根据新的种子计算随机数,并返回。

3.2 ThreadLocalRandom

Random类生成随机数原理以及不足:每个Random实例里面有一个原子性的种子变量用来记录当前的种子的值,当要生成新的随机数时候要根据当前种子计算新的种子并更新回原子变量。

多线程下使用单个Random实例生成随机数时候,多个线程同时计算随机数计算新的种子时候多个线程会竞争同一个原子变量的更新操作,由于原子变量的更新是CAS操作,同时只有一个线程会成功,那么CAS操作失败的大量线程进行自旋重试,而大量线程的自旋重试是会降低并发性能和消耗CPU资源的,为了解决这个问题,ThreadLocalRandom类应运而生。

public class RandomTest {public static void main(String[] args) {//(10)获取一个随机数生成器ThreadLocalRandom random =  ThreadLocalRandom.current();//(11)输出10个在0-5(包含0,不包含5)之间的随机数for (int i = 0; i < 10; ++i) {System.out.println(random.nextInt(5));}}
}

如上代码(10)调用ThreadLocalRandom.current()来获取当前线程的随机数生成器。下面来分析下ThreadLocalRandom的实现原理。

从名字看会让我们联想到ThreadLocal类。ThreadLocal通过让每一个线程拷贝一份变量,每个线程对变量进行操作时候实际是操作自己本地内存里面的拷贝,从而避免了对共享变量进行同步。实际上ThreadLocalRandom的实现也是这个原理。Random的缺点是多个线程会使用原子性种子变量,会导致对原子变量更新的竞争,这个原理可以通过下面图来表达:

那么如果每个线程维护自己的一个种子变量,每个线程生成随机数时候根据自己本地内存中的老的种子计算新的种子,并使用新种子更新老的种子,然后根据新种子计算随机数,就不会存在竞争问题,这会大大提高并发性能,如下图ThreadLocalRandom原理可以使用下图表达:

Thread类里面有几个变量:

    /** The current seed for a ThreadLocalRandom */@sun.misc.Contended("tlr")long threadLocalRandomSeed;/** Probe hash value; nonzero if threadLocalRandomSeed initialized */@sun.misc.Contended("tlr")int threadLocalRandomProbe;

思考问题:

  • 每个线程的初始种子怎么生成的
  • 如果保障多个线程产生的种子不一样

四、总结

本文是对拙作 java并发编程之美 一书中有关章节的提炼。本次分享首先讲解了AtomicLong的内部实现,以及存在的缺点,然后讲解了 LongAdder采用分而治之的策略通过使用多个原子变量减小单个原子变量竞争的并发度。然后简单介绍了Random,和其缺点,最后介绍了ThreadLocalRandom借用ThreadLocal的思想解决了多线程对同一个原子变量竞争锁带来的性能损耗。其实JUC包中还有其他一些经典的组件,比如fork-join框架等。

 

原文链接
本文为云栖社区原创内容,未经允许不得转载。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/519972.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

galaxy s8 android pc,手机秒变PC!三星Galaxy S8桌面模式曝光

据外媒报道&#xff0c;三星旗舰手机Galaxy S8/S8 Plus在外观上以及硬件配置上已经没有了悬念。不过一些小的改进或者是新功能还是让人对Galaxy S8充满期待。日前&#xff0c;传闻中的Galaxy S8桌面模式终于被曝光。三星Galaxy S8桌面模式曝光(图片来自kkj)报道称&#xff0c;G…

2020年进军 AI,想年薪 40 万,没这个能力不行

前几天&#xff0c;《百度沸点&#xff1a;2019年度科技热词》来了&#xff01;百度沸点&#xff1a;2019年度科技热词 AI排名第一2019年可以说是AI全面落地和商用的一年&#xff0c;产业智能化成为各个行业重点关注的发展方向&#xff0c;交通、工业、农业、医疗等主流行业无一…

重磅公开!阿里语音识别模型端核心技术,让你“听”见未来

语音识别技术作为人工智能技术中的重要组成部分&#xff0c;成为影响人机交互的核心组件之一&#xff0c;从各种智能家用IoT设备的语音交互能力&#xff0c;到公共服务、智慧政务等场合的应用&#xff0c;语音识别技术正在影响着人们生活的方方面面。 本文将全面介绍阿里云语音…

linux搭建SonarQube代码质量平台_Oracle 最新详细版本

文章目录一、最低配置要求1. JDK版本要求2. 数据库版本要求3. 支持浏览器版本二、软件下载安装2.1. 软件列表总览2.2. jdk11下载2.3. sonarqube下载2.4. sonar-scanner-cli2.5. Oracle 驱动三、安装实战3.1. JDK sonar-scanner3.2. sonarqube3.3. oracle驱动3.4. 启动sonar3.4.…

2018年AI和ML(NLP、计算机视觉、强化学习)技术总结和2019年趋势(上)

1、简介&#xff1a; 过去几年一直是人工智能爱好者和机器学习专业人士最幸福的时光。因为这些技术已经发展成为主流&#xff0c;并且正在影响着数百万人的生活。各国现在都有专门的人工智能规划和预算&#xff0c;以确保在这场比赛中保持优势。 数据科学从业人员也是如此&am…

2018最佳GAN论文回顾(下)

继上一篇《2018最佳GAN论文回顾&#xff08;上&#xff09;》&#xff0c;我又继续介绍了一个对于GAN的基于样式的生成器体系结构的新论文&#xff0c;提出了一个新的模型来应对这种挑战。 一种用于生成式对抗网络的基于生成器体系结构的方式&#xff08;A Style-Based Genera…

云+X案例展 | 民生类:浪潮云打卡人间仙境张家界

本案例由浪潮投递并参与评选&#xff0c;CSDN云计算独家全网首发&#xff1b;更多关于【云X 案例征集】的相关信息&#xff0c;点击了解详情丨挖掘展现更多优秀案例&#xff0c;为不同行业领域带来启迪&#xff0c;进而推动整个“云行业”的健康发展。“仙凡共界武陵门&#xf…

云栖专辑 | 阿里开发者们的第19个感悟:Simple is better.

2015年12月20日&#xff0c;云栖社区上线。2018年12月20日&#xff0c;云栖社区3岁。 阿里巴巴常说“晴天修屋顶”。 在我们看来&#xff0c;寒冬中&#xff0c;最值得投资的是学习&#xff0c;是增厚的知识储备。 所以社区特别制作了这个专辑——分享给开发者们20个弥足珍贵的…

使html表格可编辑状态,js+Html实现表格可编辑操作

本文实例为大家分享了jsHtml实现表格可编辑操作的具体代码&#xff0c;供大家参考&#xff0c;具体内容如下功能描述&#xff1a;单击页面使单元格td变成可编辑状态&#xff0c;输入内容后&#xff0c;当单元格失去焦点时&#xff0c;保存输入的内容。点击增加行&#xff0c;在…

深度学习为图片人物换装【python代码教程】

在观看本文之前&#xff0c;请答应我要善良。昨天预告了下&#xff0c;发现很多同学对这个模型都表示出兴趣&#xff0c;甚至有好多同学后台发来照片让我帮他们脱裤子。授人以鱼不如授人以渔&#xff0c;请这些同学好自为之~ 01效果演示 本文案例使用的是开源项目instagan&am…

java通过HTTPS协议POST提交接收JSON格式数据

文章目录一、客户端实现1. HttpsApiUtils 测试方法2. 返回报文监控二、服务端实现2.1. 配置SSL 实现HTTPS2.2. 添加post接口方法2.3. 服务端监控三、进阶测试3.1. 客户端发送对象3.2. 服务端监控3.3. 客户端解析返回报文一、客户端实现 声明&#xff1a;不用引入任何第三方jar…

2018年AI和ML(NLP、计算机视觉、强化学习)技术总结和2019年趋势(下)

4、工具和库 工具和库是数据科学家的基础。我参与了大量关于哪种工具最好的辩论&#xff0c;哪个框架会取代另一个&#xff0c;哪个库是经济计算的缩影等等。 但有一点共识--我们需要掌握该领域的最新工具&#xff0c;否则就有被淘汰的风险。 Python取代其他所有事物并将自己…

Elasticsearch7.15.2 出现 node validation exception 的问题处理

3个异常如下&#xff1a; [1]: max file descriptors [65535] for elasticsearch process is too low, increase to at least [65536][2]: memory locking requested for elasticsearch process but memory is not locked[3]: max virtual memory areas vm.max_map_count [6553…

最强NLP模型BERT可视化学习

2018年是自然语言处理&#xff08;Natural Language Processing, NLP&#xff09;领域的转折点&#xff0c;一系列深度学习模型在智能问答及情感分类等NLP任务中均取得了最先进的成果。近期&#xff0c;谷歌提出了BERT模型&#xff0c;在各种任务上表现卓越&#xff0c;有人称其…

一分钟看懂通信铁塔

戳蓝字“CSDN云计算”关注我们哦&#xff01;作者 | 无线深海责编 | 阿秃说到铁塔&#xff0c;相信大家都很熟悉。我们走在路上&#xff0c;到处都可以看到它们。作为通信工程师来说&#xff0c;我们所说的铁塔&#xff0c;往往是特指那些专门用于通信用途的塔。现实生活中&…

html立体魔方图片制作,ppt怎么制作三维视图的魔方图 ppt制作三维魔方图详细教程...

很多用户在制作PPT展示图的时候&#xff0c;有时候需要制作三维立体的魔方图&#xff0c;制作步骤简单&#xff0c;不过还有很多的用户不清楚如何制作&#xff0c;那么下面小编就为大家分享PPT制作三维魔方图的详细步骤教程&#xff0c;不会制作的朋友可以参照下面的步骤教程多…

PMP考试技巧(必备)

&#xff08;一&#xff09; 关键词篇 第 1 章 引论 看到“驱动变革”——选项中找“将来状态” 看到“依赖关系”——选项中找“项目集管理” 看到“价值最大化”——选项中找“项目组合管理” 看到“可行性研究”——选项中找“商业论证” 第 2 章 项目运行环境 看到“…

IDE 插件新版本发布,开发效率 “biu” 起来了

近日&#xff0c;Cloud Toolkit正式推出了面向 IntelliJ 和 Eclipse 两个平台的新款插件&#xff0c;本文挑选了其中三个重大特性进行解读&#xff0c;点击文末官网跳转链接&#xff0c;可查看详细的版本说明。 本地应用一键部署到任何机器上IDE 内置的命令行终端文件上传到服…

爬取6271家死亡公司数据,看十年创业公司消亡史

戳蓝字“CSDN云计算”关注我们哦&#xff01;作者 | 朱小五责编 | 阿秃前段时间老罗和王校长都成为自己的创业公司成了失信人&#xff0c;小五打算上IT桔子看看他们的公司。意外发现IT桔子出了个死亡公司库&#xff08;https://www.itjuzi.com/deathCompany&#xff09;&#x…

阿里重磅开源首款自研科学计算引擎Mars,揭秘超大规模科学计算

日前&#xff0c;阿里巴巴正式对外发布了分布式科学计算引擎 Mars 的开源代码地址&#xff0c;开发者们可以在pypi上自主下载安装&#xff0c;或在Github上获取源代码并参与开发。 此前&#xff0c;早在2018年9月的杭州云栖大会上&#xff0c;阿里巴巴就公布了这项开源计划。Ma…