你还不懂可见性、有序性和原子性?

前言

今天开始,王子准备开始一个新的专栏:并发编程专栏

并发编程无论在哪门语言里,都属于高级篇,面试中也尝尝会被问到。想要深入理解并发编程机制确实不是一件容易的事,因为它涉及到计算机底层和操作系统的相关知识,如果对这部分知识不是很清楚可能会导致理解困难。

在这个专栏里,王子会尽量以白话和图片的方式剖析并发编程本质,希望可以让大家更容易理解。

今天我们就来谈一谈可见性、有序性和原子性都是什么东西。

并发编程的幕后

进入主题之前,我们先来了解一下并发编程的幕后。

随着CPU、内存和I/O设备的不断升级,它们之间一直存在着一个矛盾,就是速度不一致问题。CPU的速度高于内存,内存的速度又高于I/O设备。

我们写的代码中大多数内容都会经过内存处理,有些内容会去读写I/O设备,根据木桶理论,整体的性能取决于最慢的操作,就是I/O设备,所以单单提升CPU的性能是不够的。

为了最大化体现出CPU的性能,计算机底层主要做了三部分优化:

1.CPU增加了缓存,比内存速度更快,平衡内存的速度

2.操作系统增加了进程和线程,可以对CPU分时复用

3.编译程序会进行指令的重排,使缓存更好的发挥性能

我们平时的工作中其实一直都享受着这些优化后的成果,但同时他们也会导致一些很难找到原因的BUG。

什么是可见性

首先我们就来看看什么是可见性。

一个线程对共享变量的修改,另一个线程可以感知到,我们称其为可见性

在单核时代,其实是不存在可见性问题的,因为所有的线程都是在一个CPU中工作的,一个线程的写操作对于其他的线程一定是可见的。

但是多核CPU出现后,每个CPU都有自己的缓存,多个线程在不同的CPU中处理数据就会导致不可见问题。

假设变量v的值是1, 两个线程同时执行了v++操作,首先会从内存中读取变量v的数据到各自的CPU缓存中,这个时候两个CPU缓存中的v都是1,执行v++后,两个变量v都变成了2,然后再写回内存,内存中的变量v就变成了2。

但其实我们想看到的结果v最终应该是3才对。

在CPU1缓存中执行v++后,CPU2缓存无法感知的到,这就是可见性问题。而由于可见性问题导致的最终数据不正确,就是线程安全问题。

什么是原子性

由于I/O的速度太慢,早期的操作系统发明了多进程,就是允许某个进程执行一小段时间后,重新选择一个进程来执行,这个过程叫做任务切换,而这一小段的时间我们称其为时间片。

现在操作系统的任务切换一般指的是更轻量级的线程切换,java的并发编程是基于多线程的,自然也会存在线程切换。

一般会在时间片结束的时候进行线程切换,java语言中执行的一段简单的代码往往需要多条CPU的指令实现,比如count++这部分代码,至少需要三条CPU指令:

1.首先把count从内存中读取到CPU的寄存器中

2.在寄存器中执行+1操作

3.最后将count的值写入内存中(可能写入到CPU的缓存中)

而线程切换是可以发生在任意的一条CPU指令执行之后的,注意,这里说的是CPU的指令,而不是java语言中的指令,对于上面的三条指令来说,我们假设 count=0,如果线程 A 在指令 1 执行完后做线程切换,线程 A 和线程 B 按照下图的顺序执行,那么我们会发现两个线程都执行了 count++ 的操作,但是得到的结果不是我们期望的 2,而是 1。

这就是线程切换导致的数据错误问题,我们把一个或者多个操作在 CPU 执行的过程中不被中断的特性称为原子性,CPU 能保证的原子操作是 CPU 指令级别的,而不是高级语言的操作符,这是违背我们直觉的地方。因此,很多时候我们需要在高级语言层面保证操作的原子性。

什么是有序性

有序性指的是程序按照代码的先后顺序执行。编译器为了优化性能,有时候会改变程序中语句的先后顺序,例如程序中:“x=1;y=2;”编译器优化后可能变成“y=2;x=1;”。

在这个例子中,编译器调整了语句的顺序,但是不影响程序的最终结果。不过有时候调整了语句的顺序可能导致意想不到的 Bug。

在 Java 领域一个经典的案例就是利用双重检查创建单例对象,代码如下:

public class Singleton {

static Singleton instance;

static Singleton getInstance(){

if (instance == null) {

synchronized(Singleton.class) {

if (instance == null)

instance = new Singleton();

}

}

return instance;

}

}

假设有两个线程 A、B 同时调用 getInstance() 方法,他们会同时发现 instance == null ,于是同时对 Singleton.class 加锁,此时 JVM 保证只有一个线程能够加锁成功(假设是线程 A),另外一个线程则会处于等待状态(假设是线程 B);线程 A 会创建一个 Singleton 实例,之后释放锁,锁释放后,线程 B 被唤醒,线程 B 再次尝试加锁,此时是可以加锁成功的,加锁成功后,线程 B 检查 instance == null 时会发现,已经创建过 Singleton 实例了,所以线程 B 不会再创建一个 Singleton 实例。

这个过程看上去是不是无懈可击,没有漏洞?

答案是否定的,问题就出在了new操作上,我们以为的new操作是这样的:

1.分配一块内存空间

2.在这块内存空间上初始化Singleton实例对象

3.把这个对象的内存地址赋值给instance变量

但实际上由于指令重排,优化后的过程是这样的:

1.分配一块内存空间

2.把这快内存空间的内存地址赋值给instance变量

3.在这块内存空间上初始化Singleton实例对象

那么这样调换顺序后会发生什么呢?

我们假设线程 A 先执行 getInstance() 方法,当执行完指令 2 时恰好发生了线程切换,切换到了线程 B 上;如果此时线程 B 也执行 getInstance() 方法,那么线程 B 在执行第一个判断时会发现 instance != null ,所以直接返回 instance,而此时的 instance 是没有初始化过的,如果我们这个时候访问 instance 的成员变量就可能触发空指针异常。

总结

使用并发编程开发,往往会出现很多难以找到原因的BUG,通过对可见性、有序性和原子性的分析,可以为我们排查并发导致的BUG提供一些思路。

CPU缓存会导致可见性

指令重排会导致有序性

线程切换会导致原子性

以上就是本篇文章的三个核心内容,那我们下篇文章继续。

(0)

相关推荐

  • volatile关键字的作用

    volatile关键字的作用 1.java内存模型. 如上图所示,所有线程的共享变量都存储在主内存中,每个线程都有一个独立的工作内存,每个线程不直接操作在主内存中的变量,而是将主内存上变量的副本放进自 ...

  • 【JAVA并发第四篇】线程安全

    【JAVA并发第四篇】线程安全

  • 全体新股民请注意,如果你还不懂用成交量选...

    全体新股民请注意,如果你还不懂用成交量选股,如果你炒股还在坐电梯,那以下的内容千万要收藏:经典成交量选股9大原则,帮你快速提高炒股水平,揭秘成交量选股的9点精髓: 1.每个位置出现的成交量变化决定了股 ...

  • 汇报工作总发怵?那是你还不懂这些方法

    向领导汇报工作是每一名机关干部履行职责的基本功,如何汇报工作呢,主要把握好以下三个方面. 汇报什么--汇报内容的确定 汇报工作可分为"主动汇报"和"被动汇报". ...

  • 干工程,脚手架还不懂?详细解读教会你!

    工程建设学习网 公众号 来源:豆丁施工 如有侵权,请联系删除 一.外脚手架的作用及用途: 脚手架是为建筑施工而搭设的上料.堆料与施工作业用的临时结构架.更重要的是为我们提供安全的行走平台,主要承担从主 ...

  • 为何说买车要选带天窗的?天窗的4个用途,有人车开报废了还不懂

    随着如今汽车产品的日益普及后,家家户户也基本上开上了小汽车,而在大多数人买车的时候,由于对汽车性能及功能了解得不够,就很难选购到一辆适合自己的车型.我们都知道汽车有品牌和配置的区别,不同品牌的汽车及不 ...

  • 染整课堂│还不懂怎么加工摇粒绒?看这篇!

    摇粒绒 摇粒绒面料柔软,轻盈,快干,易洗,在受潮时依然可以御寒.同等条件下,是美利奴羊毛御寒性的两倍,是棉的四倍.轻便的摇粒绒行动方便,穿着舒适. 摇粒绒的主要成分是聚酯纤维(涤纶),它的好处是强度高 ...

  • 90%的股民还不懂,执行这6个复盘技巧,...

    90%的股民还不懂,执行这6个复盘技巧,这使我12年从1万炒到1000千万,用心看完,看懂了,新手变高手,建议收藏! 我们为什么要做复盘呢?是为了总结经验,提升能力,改善我们的投资效果. 但是,复盘是 ...

  • 塔吊检查还不懂?10不吊、8不禁,安全员再也不怕查塔吊!

    塔吊作业操作守则 "十不吊" 1. 指挥信号不明或违章指挥不吊. 2. 超载或重量不明不吊. 3. 起重机超跨度或未按规定打支腿不吊. 4. 工件捆绑不牢或捆扎后不稳不吊. 5. ...

  • 最全面CCI指标用法,还不懂用?

    [中国股市:最全面CCI指标用法,还不懂用?] 什么是CCI? CCI指标是一种超进超抛指标.所谓超进超抛指标,顾名思义,"超进",就是已经超出进场方的能力,拿进股票的人数超过了一 ...

  • 领导都有这3个弱点,抓住一个就能成为心腹,很多人还不懂!

    在公司里,不仅要跟同事相处和睦,更要想办法跟领导亲近,得领导欢心.虽然领导都是高高在上的姿态,但其实剥离了身份和职位来看,他们也是从属于公司的员工,所以领导也是普通人.抓住了他的软肋,你就是他的人.领 ...