别再搞混JVM内存结构和Java内存模型(二)

上一篇介绍了JVM的大部分内存结构,这一篇就介绍执行引擎和Java内存模型。上一篇

1 Java内存模型(JMM,Java Memory Model)

java内存模型是java虚拟机规范定义的,用来屏蔽掉java程序在各种不同的硬件和操作系统对内存的访问的差异,这样就可以实现java程序在各种不同的平台上都能达到内存访问的一致性。可以避免像c++等直接使用物理硬件和操作系统的内存模型在不同操作系统和硬件平台下表现不同,比如有些c/c++程序可能在windows平台运行正常,而在linux平台却运行有问题。(JVM实现不同会造成“翻译”的效果不同,不同CPU平台的机器指令有千差万别,无法保证同一份代码并发下的效果一致。所以需要一套统一的规范来约束JVM的翻译过程,保证并发效果一致性)

1.2 主内存与工作内存

Java内存模型规定了所有的变量都存储在主内存中,每个线程还有自己的工作内存,线程的工作内存中保存了被该线程使用的变量的主内存副本,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的数据.不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成。
在这里插入图片描述

1.3 volatile

volatile可以说是JVM提供的最轻量级的同步机制,当一个变量被定义成volatile之后,他将具备两项特性:第一项是保证此变量对所有线程的可见性,这里的"可见性"是指当一条线程修改了这个变量的值,新值对于其他线程来说是可以立即得知的.而普通变量并不能做到这一点,普通变量的值在线程间传递时均需要通过主内存来完成。

注意:基于volatile变量的运算在并发下线程是安全的这句话不对,因为Java里面的运算操作并不是原子操作。假设两个线程a、b同时取到了主内存的值,是0,这是没有问题的,在进行++操作的时候假设线程a执行到一半,线程b执行完了,这时线程b立即同步给了主内存,主内存的值为1,而线程a此时也执行完了,同步给了主内存,此时的值仍然是1,线程b的结果被覆盖掉了。

volatile变量只能保证可见性,在不符合以下两条规则的运算场景中,我们仍然要通过加锁(使用synchronized,java.util.concurrent中的锁或原子类)来保证原子性:
(1)运算结果并不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值
(2)变量不需要与其他的状态变量共同参与不变约束。

1.4 原子性,可见性与有序性

原子性
原子性指一系列的操作,要么全部执行成功,要么全部不执行,不会出现执行一半的情况,是不可分的。
由Java内存模型来直接保证原子性的变量操作包括read、load、use、assign、store、write这6个动作,虽然存在long和double的特例,但基本可以忽律不计,目前虚拟机基本都对其实现了原子性。如果需要更大范围的控制,lock和unlock也可以满足需求。lock和unlock虽然没有被虚拟机直接开给用户使用,但是提供了字节码层次的指令monitorenter和monitorexit对应这两个操作,对应到java代码就是synchronized关键字,因此在synchronized块之间的代码都具有原子性。
可见性
可见性是指一个线程修改了一个变量的值后,其他线程立即可以感知到这个值的修改。正如前面所说,volatile类型的变量在修改后会立即同步给主内存,在使用的时候会从主内存重新读取,是依赖主内存为中介来保证多线程下变量对其他线程的可见性的。
除了volatile,synchronized和final也可以实现可见性。synchronized关键字是通过unlock之前必须把变量同步回主内存来实现的,final则是在初始化后就不会更改,所以只要在初始化过程中没有把this指针传递出去也能保证对其他线程的可见性。
有序性
有序性从不同的角度来看是不同的。单纯单线程来看都是有序的,但到了多线程就会跟我们预想的不一样。可以这么说:如果在本线程内部观察,所有操作都是有序的;如果在一个线程中观察另一个线程,所有的操作都是无序的。前半句说的就是“线程内表现为串行的语义”,后半句值得是“指令重排序”现象和主内存与工作内存之间同步存在延迟的现象。
保证有序性的关键字有volatile和synchronized,volatile禁止了指令重排序,而synchronized则由“一个变量在同一时刻只能被一个线程对其进行lock操作”来保证。

1.5 重排序

重排序参考文章

1.6 先行发生原则

先行发生原则是Java内存模型中定义的两个操作之间的偏序关系。比如说操作A先行发生于操作B,那么在B操作发生之前,A操作产生的“影响”都会被操作B感知到。这里的影响是指修改了内存中的共享变量、发送了消息、调用了方法等。个人觉得更直白一些就是有可能对操作B的结果有影响的都会被B感知到,对B操作的结果没有影响的是否感知到没有太大关系。

Java内存模型自带先行发生原则
1.程序次序原则
在一个线程内一段代码的执行结果是有序的。虽然还会指令重排,但是随便它怎么排,结果是按照我们代码的顺序生成的不会变!

扫描二维码关注公众号,回复: 11436339 查看本文章

2.管程锁定规则
无论是在单线程环境还是多线程环境,对于同一个锁来说,一个线程对这个锁解锁之后,另一个线程获取了这个锁都能看到前一个线程的操作结果!(管程是一种通用的同步原语,synchronized就是管程的实现)

3.volatile变量规则
对一个volatile变量的写操作先行发生与后面对这个变量的读操作,这里的后面是指时间上的先后顺序(后面读的操作一定知道前面修改过了)

4.线程启动规则
在主线程A执行过程中,启动子线程B,那么线程A在启动子线程B之前对共享变量的修改结果对线程B可见。

5.线程终止规则
在主线程A执行过程中,子线程B终止,那么线程B在终止之前对共享变量的修改结果在线程A中可见。

6.线程中断规则
对线程interrupt()方法的调用先行发生于被中断线程代码检测到中断事件的发生,可以通过Thread.interrupted()检测到是否发生中断。

7.对象终结规则
一个对象的初始化完成先行发生于他的finalize方法的执行,也就是初始化方法先行发生于finalize方法

8.传递性
如果操作A先行发生于操作B,操作B先行发生于操作C,那么操作A先行发生于操作C。

2 执行引擎

待更

2.1 解释器

待更

2.2 垃圾收集器

待更

猜你喜欢

转载自blog.csdn.net/weixin_45593271/article/details/107298871
今日推荐