美文网首页
内存模型

内存模型

作者: couriravant | 来源:发表于2019-12-11 15:49 被阅读0次

    内存模型

    解决什么问题

    问了解决三个问题:缓存一致性、处理器优化和指令重排

    缓存一致性问题

    单线程。cpu核心的缓存只被一个线程访问。缓存独占,不会出现访问冲突等问题。

    单核CPU,多线程。进程中的多个线程会同时访问进程中的共享数据,CPU将某块内存加载到缓存后,不同线程在访问相同的物理地址的时候,都会映射到相同的缓存位置,这样即使发生线程的切换,缓存仍然不会失效。但由于任何时刻只能有一个线程在执行,因此不会出现缓存访问冲突。

    多核CPU,多线程。每个核都至少有一个L1 缓存。多个线程访问进程中的某个共享内存,且这多个线程分别在不同的核心上执行,则每个核心都会在各自的caehe中保留一份共享内存的缓冲。由于多核是可以并行的,可能会出现多个线程同时写各自的缓存的情况,而各自的cache之间的数据就有可能不同。

    在CPU和主存之间增加缓存,在多线程场景下就可能存在缓存一致性问题,也就是说,在多核CPU中,每个核的自己的缓存中,关于同一个数据的缓存内容可能不一致。

    处理器优化和指令重排

    上面提到在在CPU和主存之间增加缓存,在多线程场景下会存在缓存一致性问题。除了这种情况,还有一种硬件问题也比较重要。那就是为了使处理器内部的运算单元能够尽量的被充分利用,处理器可能会对输入代码进行乱序执行处理。这就是处理器优化

    除了现在很多流行的处理器会对代码进行优化乱序处理,很多编程语言的编译器也会有类似的优化,比如Java虚拟机的即时编译器(JIT)也会做指令重排

    可想而知,如果任由处理器优化和编译器对指令重排的话,就可能导致各种各样的问题。

    我们说,并发编程,为了保证数据的安全,需要满足以下三个特性:

    原子性是指在一个操作中就是cpu不可以在中途暂停然后再调度,既不被中断操作,要不执行完成,要不就不执行。

    可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

    有序性即程序执行的顺序按照代码的先后顺序执行。

    有没有发现,缓存一致性问题其实就是可见性问题。而处理器优化是可以导致原子性问题的。指令重排即会导致有序性问题。所以,后文将不再提起硬件层面的那些概念,而是直接使用大家熟悉的原子性、可见性和有序性。

    内存模型

    为了保证共享内存的正确性(可见性、有序性、原子性),内存模型定义了共享内存系统中多线程程序读写操作行为的规范。通过这些规则来规范对内存的读写操作,从而保证指令执行的正确性。它与处理器有关、与缓存有关、与并发有关、与编译器也有关。他解决了CPU多级缓存、处理器优化、指令重排等导致的内存访问问题,保证了并发场景下的一致性、原子性和有序性。

    内存模型解决并发问题主要采用两种方式:限制处理器优化使用内存屏障

    java 内存模型

    Java程序是需要运行在Java虚拟机上面的,Java内存模型(Java Memory Model ,JMM)就是一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问差异的,保证了Java程序在各种平台下对内存的访问都能保证效果一致的机制及规范。

    [图片上传失败...(image-171bdc-1576050500647)] [图片上传失败...(image-c2ae4e-1576050500647)]

    原子性

    在Java中,为了保证原子性,提供了两个高级的字节码指令monitorenter和monitorexit。(http://www.hollischuang.com/archives/1883 "synchronized的实现原理")文章中,介绍过,这两个字节码,在Java中对应的关键字就是synchronized。

    因此,在Java中可以使用synchronized来保证方法和代码块内的操作是原子性的。

    可见性

    Java内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值的这种依赖主内存作为传递媒介的方式来实现的。

    Java中的volatile关键字提供了一个功能,那就是被其修饰的变量在被修改后可以立即同步到主内存,被其修饰的变量在每次是用之前都从主内存刷新。因此,可以使用volatile来保证多线程操作时变量的可见性。

    除了volatile,Java中的synchronized和final两个关键字也可以实现可见性。只不过实现方式不同。

    有序性

    在Java中,可以使用synchronized和volatile来保证多线程之间操作的有序性。实现方式有所区别:

    volatile关键字会禁止指令重排。synchronized关键字保证同一时刻只允许一条线程操作。

    好了,这里简单的介绍完了Java并发编程中解决原子性、可见性以及有序性可以使用的关键字。读者可能发现了,好像synchronized关键字是万能的,他可以同时满足以上三种特性,这其实也是很多人滥用synchronized的原因。

    但是synchronized是比较影响性能的,虽然编译器提供了很多锁优化技术,但是也不建议过度使用。

    支撑Java内存模型的基础原理

    指令重排序

    在执行程序时,为了提高性能,编译器和处理器会对指令做重排序。但是,JMM确保在不同的编译器和不同的处理器平台之上,通过插入特定类型的Memory Barrier来禁止特定类型的编译器重排序和处理器重排序,为上层提供一致的内存可见性保证。

    编译器优化重排序:编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。

    指令级并行的重排序:如果不存l在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。

    内存系统的重排序:处理器使用缓存和读写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。

    数据依赖性

    如果两个操作访问同一个变量,其中一个为写操作,此时这两个操作之间存在数据依赖性。

    编译器和处理器不会改变存在数据依赖性关系的两个操作的执行顺序,即不会重排序。

    as-if-serial

    不管怎么重排序,单线程下的执行结果不能被改变,编译器、runtime和处理器都必须遵守as-if-serial语义。

    内存屏障(Memory Barrier )

    上面讲到了,通过内存屏障可以禁止特定类型处理器的重排序,从而让程序按我们预想的流程去执行。内存屏障,又称内存栅栏,是一个CPU指令,基本上它是一条这样的指令:

    保证特定操作的执行顺序。

    影响某些数据(或则是某条指令的执行结果)的内存可见性。

    编译器和CPU能够重排序指令,保证最终相同的结果,尝试优化性能。插入一条Memory Barrier会告诉编译器和CPU:不管什么指令都不能和这条Memory Barrier指令重排序。

    Memory Barrier所做的另外一件事是强制刷出各种CPU cache,如一个Write-Barrier(写入屏障)将刷出所有在Barrier之前写入 cache 的数据,因此,任何CPU上的线程都能读取到这些数据的最新版本。

    这和java有什么关系?上面java内存模型中讲到的volatile是基于Memory Barrier实现的。

    如果一个变量是volatile修饰的,JMM会在写入这个字段之后插进一个Write-Barrier指令,并在读这个字段之前插入一个Read-Barrier指令。这意味着,如果写入一个volatile变量,就可以保证:

    一个线程写入变量a后,任何线程访问该变量都会拿到最新值。

    在写入变量a之前的写入操作,其更新的数据对于其他线程也是可见的。因为Memory Barrier会刷出cache中的所有先前的写入。

    happens-before

    从jdk5开始,java使用新的JSR-133内存模型,基于happens-before的概念来阐述操作之间的内存可见性。

    在JMM中,如果一个操作的执行结果需要对另一个操作可见,那么这两个操作之间必须要存在happens-before关系,这个的两个操作既可以在同一个线程,也可以在不同的两个线程中。

    与程序员密切相关的happens-before规则如下:

    程序顺序规则:一个线程中的每个操作,happens-before于该线程中任意的后续操作。

    监视器锁规则:对一个锁的解锁操作,happens-before于随后对这个锁的加锁操作。

    volatile域规则:对一个volatile域的写操作,happens-before于任意线程后续对这个volatile域的读。

    传递性规则:如果 A happens-before B,且 B happens-before C,那么A happens-before C。

    注意:两个操作之间具有happens-before关系,并不意味前一个操作必须要在后一个操作之前执行!仅仅要求前一个操作的执行结果,对于后一个操作是可见的,且前一个操作按顺序排在后一个操作之前。

    相关文章

      网友评论

          本文标题:内存模型

          本文链接:https://www.haomeiwen.com/subject/mjaogctx.html