Java内存模型---详细总结

Java内存模型

一.硬件的效率与一致性
由于计算机的存储设备和处理器的运算速度相差几个数量级,所以现代计算机系统必须加入一层读写速度尽可能接近于处理器运算速度的高速缓存(Cache):这样将运算所使用到的数据复制到缓存中,让运算能够快速进行,并且在运算结束后再从缓存中同步回内存中,这样处理器就无需等待缓慢的读写了
基于高速缓存的存储交互很好的解决了处理器与内存的速度矛盾,但是也为计算机带来了一个新的问题:缓存一致性,再多处理器的计算机系统中,每个处理器都有自己的高速缓存,但是它们又同时共享同一主存,当多个处理器的运算都涉及到了同一块主存区域,就可能导致各自缓存的数据不一样。为了解决缓存一致性的问题,处理器之间就必须要遵循一些协议,在读写时根据协议来进行。这里写图片描述

除了增加了高速缓存之外,处理器还会对输入代码进行乱序执行优化,虽然保证了该结果和顺序执行的结果一样,但是并不保证执行顺序和输入代码的顺序是一样的,JAVA虚拟机中的即时编译也有类似的指令重排序优化。

二. Java内存模型
Java虚拟机规范试图定义一种内存模型来屏蔽各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果。
1.主内存与工作内存
Java内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝,线程对该变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变量。不能同线程之间也无法访问到对方线程中的变量,线程间变量值的传递均需要通过主内存来完成,三者交互图如下
这里写图片描述

2.内存间的交互操作
Java内存模型中定义了8中操作来完成一个变量从主内存拷贝到工作内存,再从工作内存中同步回主内存中的实现细节,虚拟机在实现时必须保证下面的每一种操作都是原子的,不可再分的。
①lock(锁定):作用于主内存的变量,它吧一个变量标识为一个线程独占的状态。
②unlock(解锁):作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才能被其他线程锁定。
③read(读取):作用于主内存变量,把一个变量的值从主内存中读取到工作内存中,以便load动作使用。
④load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
⑤use(使用):作用于工作内存中的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个使用到变量值得字节码指令时将会执行这个操作。
⑥assign(赋值):作用于工作内存的变量,它把一个从执行引擎接受到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
⑦store(存储):作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中,以便随后的write操作使用。
⑧write(写入):作用于主内存的变量,它把store操作从工作内存中得到的变量值放入到主内存的变量中。
如果要把一个变量从主内存复制到工作内存,那就要顺序的执行read和load操作,如果要把变量从工作内存同步回主内存,就要顺序的执行store和write操作,但是java内存模型只要求上述两个操作是按顺序执行,而没有保证是连续执行。也就是read和load,以及store和write之间是可以插入其他指令的。
3.对于volatile型变量的特殊规则
关键字volatile是Java虚拟机中提供的最轻量级的同步机制,当一个变量被定义为volatile后,它将具备两种特性,第一种是保证该变量对所有线程的可见性,就是指当一条线程修改了这个变量的值,新的值对于其他线程来说是可以立即得知的,而普通变量做不到这点。
虽然,volatile变量在各个线程的工作内存中不存在一致性问题(因为在每次使用该变量之前都要先刷新,所以可以认为不存在一致性问题),但是并不能说基于volatile变量的运算在并发的情况下是安全的,这是因为Java里面的运算并非原子操作,导致了volatile变量在并发运算下也一样是不安全的。由于volatile变量只能保证可见性,所以在符合以下两条规则的运算中才能使用,否则我们仍然需要通过加锁来保证原子性。
▲运算结果并不依赖变量的当前值,或者能够确保只有单一线程修改变量的值。
▲变量不需要与其他的状态变量共同参与不变约束
volatile变量的第二种语义是禁止指令重排序,volatile修饰的变量,在赋值后会多执行一个”lock addl $0x0,(%esp)”操作,这个操作相当于一个内存屏障,指重排序时不能把后面的指令重排序到内存屏障之前,如果有单个CPU访问时,并不需要内存屏障,但是当有多个CPU访问同一块内存,而且其中有个线程在观察另一个,那就需要内存屏障来保证一致性。指令中lock前缀的作用是,使得本CPU的缓存写入了内存,就会使别的CPU或内核无效化其Cache,相当于做了一次store和write操作,所以说volatile变量的修改对其他CPU是立即可见的。
Java内存模型中对volatile变量定义的特殊规则:
▲对单一线程而言,只有线程对volatile变量执行的前一个动作是load时,线程对volatile变量才能执行use;也只有线程对volatile变量执行的后一个动作是use时,才能执行load。这就意味着,每次在工作内存中使用volatile变量时,都是从主内存刷新回的最新值。
▲只有线程对volatile变量执行的前一个动作是assign时,线程对volatile变量才能执行store;也只有线程对volatile变量执行的后一个动作是store时,才能执行assign。这就保证了每次工作内存修改了变量的值都会立刻同步回主内存中,用于保证其他线程可以看到自己对变量的修改。
▲如果线程对一个volatile变量先于另一个volatile变量执行use或assign动作,那么他的read或write动作也一定先于另一个volatile变量,这就要求volatile变量不会被指令重排序优化,保证代码的执行顺序和程序的顺序相同。
三.原子性,可见性与有序性
1.原子性:由Java内存模型来直接保证原子性的变量操作有read,load,assign,use,store和write,大致上可以认为基本数据类型的访问读写是具备原子性的,如果需要一个更大范围的原子性保证,Java内存模型还提供的lock和unlock操作,虽然没有直接开放给用户,但是更高层次的字节码指令monitorenter和monitorexit来隐式的使用这两个操作,对应到Java代码中就是同步块——synchronized关键字,因此在synchronized块之间的操作也具备原子性。
2.可见性:可见性是指当一个线程修改了共享变量的值,其他线程能够立即得知这个修改。volatile的特殊规则保证了新值能够立即同步到主内存,以及每次使用前都能以及从主内存刷新,因此可以说volatile保证了多线程操作时变量的可见性。除此之外,synchronized和final也可以实现可见性。同步块的可见性是因为在对一个变量实行unlock之前必须要先把此变量同步回主内存中。而final修饰的字段在构造器中一旦初始化完成,并且构造器没有把this的引用传递出去,那其他线程就能看到final字段的值了。
3.有序性:总结为一句话就是,如果在本线程内观察,所有操作都是有序的,但是如果在另外一个线程观察,所有的操作都是无序的。前半句指“线程内表现为串行的语义”,后半句指“指令重排序”和“工作内存与主内存同步延迟”。

                                               ---以上内容来自《深入理解JVM虚拟机》
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值