java内存模型(一)

在介绍Java内存模型之前,我们回顾一下计算机的内存模型,因为Java很多的并发操作和计算机的内存模型息息相关。

一、计算机硬件内存模型

现代CPU的运算速度远远高于物理内存的读写速度,但处理器在运算的过程中,又避免不了与内存的读写交互,所以很多时候会出现处理器要等待内存数据读写完成后才能进行下一步运算。为了提高处理器的运算速度,现在的计算机系统为处理器添加了一层读写速度接近CPU运算速度的缓存,这样,处理任务时需要将数据复制到缓存中,运算速度结束后,将缓存中的数据同步写回内存。交互的关系图为:

但是,现代计算机的CPU都是多核的CPU,每个CPU也都有自己的高速缓存,这就带来了一个新的问题,怎么保证多个CPU在更改同一块内存区域的时候,数据以谁为准?这个问题的就是高速缓存一致性问题,目前有相对应的协议,比如(MSI,MESI,MOSI,firefly)等缓存协议,解决缓存一致性问题。

二、什么是内存模型 

内存模型可以理解为在特定的操作协议下,对计算机内存和高速缓存进行读写的抽象。通俗一点就是:内存模型屏蔽了与高速缓存的交互,变为直接与主内存交互的存在。

当然,为了获取CPU更好的性能,处理器还对获取的代码做了一些优化处理,比如调整指令的顺序,也就是常说的指令重排。

三、Java内存模型

3.1 主内存

    Java内存模型中规定,所有的变量都存储在主内存中。这里的变量主要值共享变量,比如实例变量、类变量等,构成线程之间的竞争关系,对于线程私有的变量,比如程序计数器指令等,不在此范围。

3.2 工作内存

Java规定每个线程有自己的工作内存,工作内存中保存了被线程使用到的变量,这些变量来自主内存变量的副本copy。不同线程之间,工作内存相互隔离。

线程工作时,首先把使用到的变量从主内存拷贝到工作内存,线程运行结束后,再把自己内存中的变量写回到主内存。多个线程之间交互信息只能通过主内存。

3.3 Java内存模型

Java内存模型定义了线程、工作内存、主内存之间的关系

 需要注意的是,对于JMM的定义,主要集中在堆内存、方法区等线程共享区域。

3.4 工作内存与主内存的数据交换的细节

JMM定义了8中操作来完成主内存、工作内存和执行引擎之间的交互,分别是:

  • lock:作用于主内存,把变量标记为线程独占
  • unlock:与lock相反,释放变量的lock标记
  • read:作用于主内存,把主内存的变量传递给工作内存
  • load:作用于工作内存,把read过来的值放入工作内存
  • store:作用于工作内存,把工作内存的变量传递到主内存
  • write:作用于主内存,把store过来的值写入主内存
  • use:作用与工作内存,把工作内存变量传给执行引擎
  • assign:做用于工作内存,把执行引擎的值赋给工作内存变量

3.5 Java内存模型的3个特征

  • 原子性:指一个操作不可中断,不可分割,多线程中就是一个线程开始操作,就不允许被别的线程打断;
  • 可见性:工作内存中的值一旦有变化后,需要及时的同步到主内存,其他线程在使用这个值的时候,也需要重主内存中获取最新的。
  • 有序性:指程序执行按照一定的逻辑顺序。

 四、指令重排和内存屏障

4.1 指令重排

程序执行过程中, 为了性能考虑, 编译器和CPU可能会对指令重新排序。大多数现代微处理器都会采用将指令乱序执行(out-of-order execution,简称OOE),在条件允许的情况下,直接运行当前有能力立即执行的后续指令,避开获取下一条指令所需数据时造成的等待。通过乱序执行的技术,处理器可以大大提高执行效率。

指令重排序按级别有三种:

  • 编译器重排序:编译器在不改变单线程程序语义的前提下,可重新安排语句的执行顺序
  • 指令级重排序:处理器采用指令级并行技术将多条指令重叠执行。指令重排遵循基本的语义:
  • 内存级重排序:由于处理器使用缓存和读写缓冲区,使得加载(load)和存储(store)操作看上去可能是在乱序执行。

As-if-serial语义:指所有的动作(Action)都可以为了优化而被重排序,但是必须保证它们重排序后的结果和程序代码本身的应有结果是一致的。Java编译器、运行时和处理器都会保证单线程下的as-if-serial语义。

happens-before语义:happens-before保证如果A、B两个操作存在happens before关系,那么A操作的结果一定对B可见;

4.2 内存屏障

指令重排序会导致多线程执行的无序,JMM通过内存屏障来禁止某些指令重排序,总共4类:

LoadLoad:前面的load会先于后面的load装载

StoreStore:前面的store会先于后面的store执行,也就是保证内存可见性

LoadStore:前面的load先于后面的store执行

StoreLoad:前面的store先于后面的Load执行

五、volatile、final、锁的内存语义

内存屏障:又称内存栅栏,是一个CPU指令,它主要作用于:一是保证特定操作的执行顺序,二是保证某些变量的内存可见性。

volatite关键字:会在最终编译出来的指令上加上lock前缀,lock前缀的指令做三件事情:1)防止指令重排序;2)锁住总线或者使用锁定缓存来保证执行的原子性;3)把缓冲区的所有数据都写回主内存,并保证其他处理器缓存的该变量失效。

对volatile变量的写操作,前面插入StoreStore屏障,后面插入StoreLoad屏障;对volatile变量的读操作,后面会插入两个屏障,分别是LoadLoad、LoadStore。

final关键字:final本质上定义是final域与构造对象的引用之间的内存屏障。构造函数对final变量的写入,与对构造函数对象引用的读,不能重排序,插入了storeStore屏障。读含有final变量的对象的引用,与读final变量不能指令重排序,插入loadload屏障,保证先读到对象引用,在读final变量的值,也就是只要对象构造完成,并且在构造函数中将final值写入,另外一个线程肯定可以读到,这是JMM的保证。

ReentrantLock中 有private volatile int state,本质上是用的volatile的内存语义。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值