【高并发】解密导致并发问题的第二个幕后黑手——原子性问题(文末有福利)
写在前面
大冰:小菜童鞋,昨天讲解的内容复习了吗?
小菜:复习了大冰哥,昨天的内容干货满满啊,感觉自己收获很大。
大冰:那你说说昨天都讲了哪些内容呢?
小菜:昨天主要讲了线程的可见性和可见性问题。可见性是指一个线程对共享变量的修改,另一个线程能够立刻看到,如果不能立刻看到,就可能会产生可见性问题。在单核CPU上是不存在可见性问题的,可见性问题主要存在于运行在多核CPU上的并发程序。归根结底,可见性问题还是由CPU的缓存导致的,而缓存导致的可见性问题是导致诸多诡异的并发编程问题的“幕后黑手”之一。
大冰:很好,小菜童鞋,复习的不错,今天,我们继续讲并发问题的第二个“幕后黑手”——线程切换带来的原子性问题,这个知识点也是非常重要的,一定要好好听。
注意:文末有福利!!!
原子性
原子性是指一个或者多个操作在CPU中执行的过程不被中断的特性。原子性操作一旦开始运行,就会一直到运行结束为止,中间不会有中断的情况发生。
我们也可以这样理解原子性,就是线程在执行一系列操作时,这些操作会被当做一个不可拆分的整体执行,这些操作要么全部执行,要么全部不执行,不会存在只执行一部分的情况,这就是原子性操作。
关于原子性操作一个典型的场景就是转账,例如,小明和小刚的账户余额都是200元,此时小明给小刚转账100元,如果转账成功,则小明的账户余额为100元,小刚的账户余额为300元;如果转账失败,则小明和小刚的账户余额仍然为200元。不会存在小明账户为100元,小刚账户为200元,或者小明账户为200元,小刚账户为300元的情况。
这里,小明给小刚转账100元的操作,就是一个原子性操作,它涉及小明账户余额减少100元,小刚账户余额增加100元的操作,这两个操作是一个不可分割的整体,要么全部执行,要么全部不执行。
小明给小刚转账成功,则如下所示。
小明给小刚转账失败,则如下所示。
不会出现小明账户为100元,小刚账户为200元的情况。
也不会出现小明账户为200元,小刚账户为300元的情况。
线程切换
在并发编程中,往往设置的线程数目会大于CPU数目,而每个CPU在同一时刻只能被一个线程使用。而CPU资源的分配采用了时间片轮转策略,也就是给每个线程分配一个时间片,线程在这个时间片内占用CPU的资源来执行任务。当占用CPU资源的线程执行完任务后,会让出CPU的资源供其他线程运行,这就是任务切换,也叫做线程切换或者线程的上下文切换。
如果大家还是不太理解的话,我们可以用下面的图来模拟线程在CPU中的切换过程。
在图中存在线程A和线程B两个线程,其中线程A和线程B中的每个小方块代表此时线程占有CPU资源并执行任务,这个小方块占有的时间,被称为时间片,在这个时间片中,占有CPU资源的线程会在CPU上执行,未占有CPU资源的线程则不会在CPU上执行。而每个虚线部分就代表了此时的线程不占用CPU资源。CPU会在线程A和线程B之间频繁切换。
原子性问题
理解了什么是原子性,再看什么是原子性问题就比较简单了。
原子性问题是指一个或者多个操作在CPU中执行的过程中出现了被中断的情况。
线程在执行某项操作时,此时如果CPU发生了线程切换,CPU转而去执行其他的任务,中断了当前线程执行的操作,这就会造成原子性问题。
如果你还不能理解的话,我们来举一个例子:假设你在银行排队办理业务,小明在你前面,柜台的业务员为小明办理完业务,正好排到你时,此时银行下班了,柜台的业务员微笑着告诉你:实在不好意思,先生(女士),我们下班了,您明天再来吧!此时的你就好比是正好占有了CPU资源的线程,而柜台的业务员就是那颗发生了线程切换的CPU,她将线程切换到了下班这个线程,执行下班的操作去了。
Java中的原子性问题
在Java中,并发程序是基于多线程技术来编写的,这也会涉及到CPU的对于线程的切换问题,正是CPU中对任务的切换机制,导致了并发编程会出现原子性的诡异问题,而原子性问题,也成为了导致并发问题的第二个“幕后黑手”。
在并发编程中,往往Java语言中一条简单的语句,会对应着CPU中的多条指令,假设我们编写的ThreadTest类的代码如下所示。
package io.mykit.concurrent.lab01;/*** @author binghe* @version 1.0.0* @description 测试原子性*/
public class ThreadTest {private Long count;public Long getCount(){return count;}public void incrementCount(){count++;}
}
接下来,我们打开ThreadTest类的class文件所在的目录,在cmd命令行输入如下命令。
javap -c ThreadTest
得出如下的结果信息,如下所示。
d:>javap -c ThreadTest
Compiled from "ThreadTest.java"
public class io.mykit.concurrent.lab01.ThreadTest {public io.mykit.concurrent.lab01.ThreadTest();Code:0: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: returnpublic java.lang.Long getCount();Code:0: aload_01: getfield #2 // Field count:Ljava/lang/Long;4: areturnpublic void incrementCount();Code:0: aload_01: getfield #2 // Field count:Ljava/lang/Long;4: astore_15: aload_06: aload_07: getfield #2 // Field count:Ljava/lang/Long;10: invokevirtual #3 // Method java/lang/Long.longValue:()J13: lconst_114: ladd15: invokestatic #4 // Method java/lang/Long.valueOf:(J)Ljava/lang/Long;18: dup_x119: putfield #2 // Field count:Ljava/lang/Long;22: astore_223: aload_124: pop25: return
}
这里,我们主要关注下incrementCount()方法对应的CPU指令,如下所示。
public void incrementCount();Code:0: aload_01: getfield #2 // Field count:Ljava/lang/Long;4: astore_15: aload_06: aload_07: getfield #2 // Field count:Ljava/lang/Long;10: invokevirtual #3 // Method java/lang/Long.longValue:()J13: lconst_114: ladd15: invokestatic #4 // Method java/lang/Long.valueOf:(J)Ljava/lang/Long;18: dup_x119: putfield #2 // Field count:Ljava/lang/Long;22: astore_223: aload_124: pop25: return
可以看到,Java语言中短短的几行incrementCount()方法竟然对应着那么多的CPU指令。这些CPU指令我们大致可以分成三步。
- 指令1:把变量count从内存加载的CPU寄存器。
- 指令2:在寄存器中执行count++操作。
- 指令3:将结果写入缓存(可能是CPU缓存,也可能是内存)。
在操作系统执行线程切换时,可能发生在任何一条CPU指令完成后,而不是程序中的某条语句完成后。如果线程A执行完指令1后,操作系统发生了线程切换,当两个线程都执行count++操作后,得到的结果是1而不是2。这里,我们可以使用下图来表示这个过程。
由上图,我们可以看出:线程A将count=0加载到CPU的寄存器后,发生了线程切换。此时内存中的count值仍然为0,线程B将count=0加载到寄存器,执行count++操作,并将count=1写到内存。此时,CPU切换到线程A,执行线程A中的count++操作后,线程A中的count值为1,线程A将count=1写入内存,此时内存中的count值最终为1。
所以,如果在CPU中存在正在执行的线程,恰好此时CPU发生了线程切换,则可能会导致原子性问题,这也是导致并发编程频繁出问题的根源之一。我们只有充分理解并掌握线程的原子性以及引起原子性问题的根源,并在日常工作中时刻注意编写的并发程序是否存在原子性问题,才能更好的编写出并发程序。
总结
缓存带来的可见性问题、线程切换带来的原子性问题和编译优化带来的有序性问题,是导致并发编程频繁出现诡异问题的三个源头,我们已经介绍了缓存带来的可见性问题和线程切换带来的原子性问题。下一篇中,我们继续深耕高并发中的有序性问题。
写在最后
大冰:好了,今天就是我们讲的主要内容了,今天的内容同样最重要,回去后要好好复习。
小菜:好的,大冰哥,一定好好复习。
文末福利
微信搜索并关注「 冰河技术 」微信公众号,发送「 jvm 」领取全套《JVM指令手册》。
最后,附上并发编程需要掌握的核心技能知识图,祝大家在学习并发编程时,少走弯路。
【高并发】解密导致并发问题的第二个幕后黑手——原子性问题(文末有福利)相关推荐
- 【高并发】解密导致并发问题的第三个幕后黑手——有序性问题
写在前面 大冰:小菜童鞋,昨天的内容复习了吗? 小菜:复习了大冰哥,昨天的内容干货满满啊,感觉自己收获很大. 大冰:那你说说昨天都讲了哪些内容呢? 小菜:昨天主要讲了原子性.线程切换和原子性问题,在编 ...
- 由于幸存者偏差,导致强变量在后续迭代中逐渐削弱甚至相反怎么办|文末有福利
解析: 幸存者偏差(SurvivorshipBias)与样本不均衡(Imbalance Learning)问题都是由于风控模型的拒绝属性导致的.但表现形式略有不同.幸存者偏差是指,每次模型迭代时,使用 ...
- 【高并发】亿级流量场景下如何实现分布式限流?看完我彻底懂了!!(文末有福利)
写在前面 在互联网应用中,高并发系统会面临一个重大的挑战,那就是大量流高并发访问,比如:天猫的双十一.京东618.秒杀.抢购促销等,这些都是典型的大流量高并发场景.关于秒杀,小伙伴们可以参见我的另一篇 ...
- 【文末有福利】为何美国的科研既能得诺贝尔奖,又能产生高科技产品?
美国历史上有三个叫布什的大人物,两位担任总统的布什和本书的作者范内瓦·布什,后者和前两者其实没有什么关系.不过,如果把两位布什总统加起来放在天平的一边,范内瓦·布什放在另一边,真的很难讲哪一边更重. ...
- 如何制作一个高转化的广告落地页(文末有福利)
在这个用户红利期逐渐消失的网络信息化年代,流量越来越贵,转化率越来越重要,因而落地页的重要性就凸显出来. 对于广告优化师/广告运营来说,制作广告落地页是必备技能,但制作一个高转化的广告落地页就是加分项 ...
- 【高并发】一文解密诡异并发问题的第一个幕后黑手——可见性问题
写在前面 大冰:小菜童鞋,昨天讲解的内容复习了吗? 小菜:复习了,大冰哥. 大冰:那你说说我们昨天都讲了哪些内容呢? 小菜:昨天讲了并发编程的难点,由这些难点引出我们需要了解导致这些问题的" ...
- 解密诡异并发问题的幕后黑手:可见性问题
摘要:可见性问题还是由CPU的缓存导致的,而缓存导致的可见性问题是导致诸多诡异的并发编程问题的"幕后黑手"之一. 本文分享自华为云社区<[高并发]一文解密诡异并发问题的第一个 ...
- java设计模式并发_[高并发Java 七] 并发设计模式
[高并发Java 七] 并发设计模式 [高并发Java 七] 并发设计模式 为什么80%的码农都做不了架构师?>>> 在软件工程中,设计模式(design pattern)是对软件设 ...
- java 并发框架源码_某网Java并发编程高阶技术-高性能并发框架源码解析与实战(云盘下载)...
第1章 课程介绍(Java并发编程进阶课程) 什么是Disruptor?它一个高性能的异步处理框架,号称"单线程每秒可处理600W个订单"的神器,本课程目标:彻底精通一个如此优秀的 ...
最新文章
- 07-主队列和全局队列
- 英文期刊催稿信模板_手把手教你写投稿信,另附查尔斯沃思高质量模板
- Python easy_insatll 安装包
- vue获取url中ip_Kubernetes 集群中这样获取客户端真实 IP
- 【数字识别】基于matlab离散Hopfield神经网络数字识别【含Matlab源码 226期】
- springboot 整合 shiro (Web Applications)避坑一 ,请看shiro官网
- ios安卓模拟器_雷电模拟器4.0.5去广告修改版
- 使用小爱同学来控制电脑关机
- UVC 1.0 和 UVC 1.1的差别
- CouchDB小全 --- 网上很棒的介绍CouchDB的博文
- Oh!兄嘚,想要提高技能?先从锁的优化开始吧
- 纯函数、柯里化、组合函数的解析以及代码实现
- java面试题目整理
- 此次519暴跌的几点感触 2021-05-21
- 编译OpenArkCompiler出现进程被kil
- C语言 | 函数实现比较大小
- 10.28字符串加密等
- 文件格式转换服务器,文件格式转换方法
- Swoft入门及技术指南
- 流利说 语音技巧汇总
热门文章
- HTML入门笔记(带源文件)
- java盘盈盘亏_反映财产物资的盘盈、盘亏和毁损情况,应当设( )科目。
- Camera2 三预览
- AD(altium designer)15原理图与PCB设计教程(四)——电路原理图设计进阶
- centos mysql 1146_MySQL 主从复制 出现1146error,求如何解决?
- 等效于35mm相机焦距的计算方法
- re -12 buuctf [Zer0pts2020]easy strcmp
- 微信小程序-使用对象格式数据进行遍历的坑(对象格式的赋值及遍历顺序)
- (初学者)关于C语言中退格键(\b)的初步了解
- 2018下半年Android面试历程