专注于Java领域优质技术,欢迎关注

来自:石杉的架构笔记

一、前言

这篇文章给大家聊一下java并发包下的CAS相关的原子操作,以及Java 8如何改进和优化CAS操作的性能。

因为Atomic系列的原子类,无论在并发编程、JDK源码、还是各种开源项目中,都经常用到。而且在Java并发面试中,这一块也属于比较高频的考点,所以还是值得给大家聊一聊。

二、场景引入,问题凸现

好,我们正式开始!假设多个线程需要对一个变量不停的累加1,比如说下面这段代码:

实际上,上面那段代码是不ok的,因为多个线程直接这样并发的对一个data变量进行修改,是线程不安全性的行为,会导致data值的变化不遵照预期的值来改变。

举个例子,比如说20个线程分别对data执行一次data++操作,我们以为最后data的值会变成20,其实不是。

最后可能data的值是18,或者是19,都有可能,因为多线程并发操作下,就是会有这种安全问题,导致数据结果不准确。

至于为什么会不准确?那不在本文讨论的范围里,因为这个一般只要是学过java的同学,肯定都了解过多线程并发问题。

三、初步的解决方案:synchronized

所以,对于上面的代码,一般我们会改造一下,让他通过加锁的方式变成线程安全的:

这个时候,代码就是线程安全的了,因为我们加了synchronized,也就是让每个线程要进入increment()方法之前先得尝试加锁,同一时间只有一个线程能加锁,其他线程需要等待锁。

通过这样处理,就可以保证换个data每次都会累加1,不会出现数据错乱的问题。

老规矩!我们来看看下面的图,感受一下synchronized加锁下的效果和氛围,相当于N个线程一个一个的排队在更新那个数值。

但是,如此简单的data++操作,都要加一个重磅的synchronized锁来解决多线程并发问题,就有点杀鸡用牛刀,大材小用了。

虽然随着Java版本更新,也对synchronized做了很多优化,但是处理这种简单的累加操作,仍然显得“太重了”。人家synchronized是可以解决更加复杂的并发编程场景和问题的。

而且,在这个场景下,你要是用synchronized,不就相当于让各个线程串行化了么?一个接一个的排队,加锁,处理数据,释放锁,下一个再进来。

四、更高效的方案:Atomic原子类及其底层原理

对于这种简单的data++类的操作,其实我们完全可以换一种做法,java并发包下面提供了一系列的Atomic原子类,比如说AtomicInteger。

他可以保证多线程并发安全的情况下,高性能的并发更新一个数值。我们来看下面的代码:

大家看上面的代码,是不是很简单!多个线程可以并发的执行AtomicInteger的incrementAndGet()方法,意思就是给我把data的值累加1,接着返回累加后最新的值。

这个代码里,就没有看到加锁和释放锁这一说了吧!

实际上,Atomic原子类底层用的不是传统意义的锁机制,而是无锁化的CAS机制,通过CAS机制保证多线程修改一个数值的安全性

那什么是CAS呢?他的全称是:Compare and Set,也就是先比较再设置的意思。

话不多说,先上图!

我们来看上面的图,假如说有3个线程并发的要修改一个AtomicInteger的值,他们底层的机制如下:

首先,每个线程都会先获取当前的值,接着走一个原子的CAS操作,原子的意思就是这个CAS操作一定是自己完整执行完的,不会被别人打断。

然后CAS操作里,会比较一下说,唉!大兄弟!现在你的值是不是刚才我获取到的那个值啊?

如果是的话,bingo!说明没人改过这个值,那你给我设置成累加1之后的一个值好了!

同理,如果有人在执行CAS的时候,发现自己之前获取的值跟当前的值不一样,会导致CAS失败,失败之后,进入一个无限循环,再次获取值,接着执行CAS操作!

好!现在我们对照着上面的图,来看一下这整个过程:

  • 首先第一步,我们假设线程一咔嚓一下过来了,然后对AtomicInteger执行incrementAndGet()操作,他底层就会先获取AtomicInteger当前的值,这个值就是0。
  • 此时没有别的线程跟他抢!他也不管那么多,直接执行原子的CAS操作,问问人家说:兄弟,你现在值还是0吗?
  • 如果是,说明没人修改过啊!太好了,给我累加1,设置为1。于是AtomicInteger的值变为1!
  • 接着线程2和线程3同时跑了过来,因为底层不是基于锁机制,都是无锁化的CAS机制,所以他们俩可能会并发的同时执行incrementAndGet()操作。
  • 然后俩人都获取到了当前AtomicInteger的值,就是1
  • 接着线程2抢先一步发起了原子的CAS操作!注意,CAS是原子的,此时就他一个线程在执行!
  • 然后线程2问:兄弟,你现在值还是1吗?如果是,太好了,说明没人改过,我来改成2
  • 好了,此时AtomicInteger的值变为了2。关键点来了:现在线程3接着发起了CAS操作,但是他手上还是拿着之前获取到的那个1啊!
  • 线程3此时会问问说:兄弟,你现在值还是1吗?
  • 噩耗传来!!!这个时候的值是2啊!线程3哭泣了,他说,居然有人在这个期间改过值。算了,那我还是重新再获取一次值吧,于是获取到了最新的值,值为2。
  • 然后再次发起CAS操作,问问,现在值是2吗?是的!太好了,没人改,我抓紧改,此时AtomicInteger值变为3!

上述整个过程,就是所谓Atomic原子类的原理,没有基于加锁机制串行化,而是基于CAS机制:先获取一个值,然后发起CAS,比较这个值被人改过没?如果没有,就更改值!这个CAS是原子的,别人不会打断你!

通过这个机制,不需要加锁这么重量级的机制,也可以用轻量级的方式实现多个线程安全的并发的修改某个数值。

五、Java 8对CAS机制的优化

但是这个CAS有没有问题呢?肯定是有的。比如说大量的线程同时并发修改一个AtomicInteger,可能有很多线程会不停的自旋,进入一个无限重复的循环中。

这些线程不停地获取值,然后发起CAS操作,但是发现这个值被别人改过了,于是再次进入下一个循环,获取值,发起CAS操作又失败了,再次进入下一个循环。

在大量线程高并发更新AtomicInteger的时候,这种问题可能会比较明显,导致大量线程空循环,自旋转,性能和效率都不是特别好。

于是,当当当当,Java 8推出了一个新的类,LongAdder,他就是尝试使用分段CAS以及自动分段迁移的方式来大幅度提升多线程高并发执行CAS操作的性能!

在LongAdder的底层实现中,首先有一个base值,刚开始多线程来不停的累加数值,都是对base进行累加的,比如刚开始累加成了base = 5。

接着如果发现并发更新的线程数量过多,就会开始施行分段CAS的机制,也就是内部会搞一个Cell数组,每个数组是一个数值分段。

这时,让大量的线程分别去对不同Cell内部的value值进行CAS累加操作,这样就把CAS计算压力分散到了不同的Cell分段数值中了!

这样就可以大幅度的降低多线程并发更新同一个数值时出现的无限循环的问题,大幅度提升了多线程并发更新数值的性能和效率!

而且他内部实现了自动分段迁移的机制,也就是如果某个Cell的value执行CAS失败了,那么就会自动去找另外一个Cell分段内的value值进行CAS操作。

这样也解决了线程空旋转、自旋不停等待执行CAS操作的问题,让一个线程过来执行CAS时可以尽快的完成这个操作。

最后,如果你要从LongAdder中获取当前累加的总值,就会把base值和所有Cell分段数值加起来返回给你。

六、总结 & 思考

不知道大家有没有发现这种高并发访问下的分段处理机制,在很多地方都有类似的思想体现!因为高并发中的分段处理机制实际上是一个很常见和常用的并发优化手段。

在我们之前的一篇讲分布式锁的文章:(每秒上千订单场景下的分布式锁高并发优化实践!),也是用到了分段加锁以及自动分段迁移/合并加锁的一套机制,来大幅度几十倍的提升分布式锁的并发性能。

所以其实很多技术,思想都是有异曲同工之妙的。

来自:https://juejin.im/entry/5c071c105188255625585960


现在人工智能非常火爆,很多朋友都想学,但是一般的教程都是为博硕生准备的,太难看懂了。最近发现了一个非常适合小白入门的教程,不仅通俗易懂而且还很风趣幽默。所以忍不住分享一下给大家。点下面链接可以跳转到教程。

https://www.captainbed.net/suga

并发执行变成串行_大白话Java并发面试问题之Java 8如何优化CAS性能?相关推荐

  1. 并发执行变成串行_网易Java研发面试官眼中的Java并发——安全性、活跃性、性能...

    一. 安全性问题 线程安全的本质是正确性,而正确性的含义是程序按照预期执行 理论上线程安全的程序,应该要避免出现可见性问题(CPU缓存).原子性问题(线程切换)和有序性问题(编译优化) 需要分析是否存 ...

  2. 并发执行变成串行_一篇讲透如何理解数据库并发控制(纯干货)

    1.数据库并发控制的作用 1.1 事务的概念 在介绍并发控制前,首先需要了解事务.数据库提供了增删改查等几种基础操作,用户可以灵活地组合这几种操作,实现复杂的语义.在很多场景下,用户希望一组操作可以做 ...

  3. 并发执行变成串行_大神浅谈数据库并发控制 锁和 MVCC

    在学习几年编程之后,你会发现所有的问题都没有简单.快捷的解决方案,很多问题都需要权衡和妥协,而本文介绍的就是数据库在并发性能和可串行化之间做的权衡和妥协 - 并发控制机制.  如果数据库中的所有事务 ...

  4. 并发执行变成串行_一篇讲透如何理解数据库并发控制

    01数据库并发控制的作用 1.1 事务的概念 在介绍并发控制前,首先需要了解事务.数据库提供了增删改查等几种基础操作,用户可以灵活地组合这几种操作,实现复杂的语义.在很多场景下,用户希望一组操作可以做 ...

  5. 使用 JAVA 队列把高并发转为批量串行降低后端并发压力

    整体思路: 1.多用户并发请求后端接口: 2.后端会启动多个线程处理前端的请求,如果并发很大就会对后端数据库造成很大的并发压力: 3.通过 JAVA 队列把用户的并发请求转换为串行批量操作数据库: 4 ...

  6. 吃个快餐都能学到串行、并行、并发

    Java 多线程系列文章第 3 篇 这篇文章继续来唠唠概念,讲这三兄弟:串行(Serial).并行(Parallel).并发(Concurrent). 吃快餐 出门在外吃饭是一件头疼的事,用我大学舍友 ...

  7. 串行、并行、并发,别再傻傻分不清了!

    开足码力,码动人生,微信搜索[ 程序员大帝 ],关注这个一言不合就开车的的代码界老司机 本文 GitHub上已经收录 https://github.com/BeKingCoding/JavaKing ...

  8. 《Java 后端面试经》Java 基础篇

    <Java 后端面试经>专栏文章索引: <Java 后端面试经>Java 基础篇 <Java 后端面试经>Java EE 篇 <Java 后端面试经>数 ...

  9. java后端面试大全,java后端面试宝典

    文章目录 -2 flink -1 linux of view linux查看占用cup最高的10个进程的命令: 〇.分布式锁 & 分布式事务 0-1分布式锁--包含CAP理论模型 概述 分布式 ...

最新文章

  1. 表格对决CSS--一场生死之战
  2. 口腔取模过程及注意事项_数字化口 腔的发展与展望
  3. 【网络安全】 利用 EHole 进行红队快速批量打点
  4. Spring中任务调度cronExpression配置说明
  5. 广东省那么发达,为什么还有全国贫困县?
  6. Android去掉listView,gridView等系统自带阴影
  7. imgcms php,imgcms IMGCMS PHP image management system - 下载 - 搜珍网
  8. ElementUI:vue中使用elementUI时候通过SCSS修改NavMenu 导航栏高度
  9. 《用户体验要素——以用户为中心的产品设计》整理
  10. edge同步chrome书签_如何通过 iCloud 同步 chromium edge 的书签?
  11. 基于STM32F1的HMC5883L电子罗盘驱动——妈妈从此再也不担心我迷路了
  12. Python局域网内搭建文件共享服务器 上传文件
  13. 红蓝对抗---蓝队手册
  14. ssdt函数索引号_【转】SSDT索引号的获取
  15. 华为运营商级路由器配置示例 | EVdPdNd VPLS over SRv6 TE Policy(静态配置)
  16. php博客视频教程,ThinkPHP5 博客项目实战视频教程
  17. 学习博士答辩之后的感想:
  18. [UE5 C++] 免费安装JetBrains Mono字体至IDE
  19. Day374.shiro授权Shiro+jsp整合Springboot -Shiro
  20. Python爬取全国大学排名 用pyecharts进行大屏可视化

热门文章

  1. 面试题:如何实现一个深拷贝
  2. WEB前端学习四 js什么是原始类型
  3. 机器学习(Machine Learning)- 吴恩达(Andrew Ng)-学习笔记
  4. windows安装go环境变量
  5. 微信小程序setTimeout自动跳转页面
  6. Linux:WPS不能使用中文输入法
  7. Fedora 31 已经为 Python 3.8 做好了准备
  8. 基本算法--希尔排序
  9. Shell编程中Shift的用法(转)
  10. screen命令简介