本文部分内容选自<<深入理解Java虚拟机>> 以及 CrazyDailyQuestion的ArtarisCN
引言
Java 内存模型 , 即 Java Memory Model,JMM 来屏蔽各种硬件和操作系统的内存访问差异,以实现让Java 在各种平台都达到一致的访问效果.Java 内存模型规范了 JVM 如何 禁用缓存 和 编译优化 的方法.
一.Main Memory和Working Memory
- Java 规定所有的变量都存储在主内存(Main Memory)中
- 每条线程Working Memory 保存了该Thread使用的变量的Main Memory副本copy
- Thread间变量值通过 Main Memory 传递
- Thread对变量操作(赋值,读取)只能在 Working Memory 进行, Main Memory 不可读
- ThreadA 无法 直接 访问 ThreadB Working Memory 变量,同理ThreadB也是如此
下面我就用图文并茂的方式给大家展示一下: Thread,Main Memory和Working Memory的交互关系
二.内存间交互操作
那么,变量是如何通过Main Memory copy给 Working Memory,如何从 Work Memory sync 到 Main Memory的呢?
jvm实现必须保证每一种操作都是原子的,不可细分的(double 和 long比较特殊)
Java 提出了 8种操作类型
变量作用域: Main Memory
方法 | 作用 |
lock(🔒) | 把Thread 设置线程独有 Tag |
unlock(🔓) | 释放变量,给其他Thread使用 |
read(读取) | 变量从Main Memory传输到Thread 的 Working Memory途径 |
变量作用域: Working Memory
方法 | 作用 |
load(载入) | 变量值放到Working Memory 的变量副本中 |
use(使用) | Working Memory的变量值传给执行引擎,JVM收到需要执行的变量的字节码指令时候,会执行这个操作 |
assign(赋值) | 他把一个从执行引擎接收的值赋给Working Memory 的变量,JVM收到需要执行的变量的字节码指令时候,会执行这个操作 |
store(存储) | 把变量值传给 Main Memory |
变量作用域: Main Memory
方法 | 作用 |
write(写入) | Working Memory的变量放到Main Memory中 |
操作规则:
太官方,主要是为了解决Java并发安全,Android用的不太多,只想理清流程,不想看细节~
三.volatile
没有被volatile位数据可以分割为两个32位进行操作,volatile和syhnronized功能类似,解决多Thread竞争问题
操作规则:
- 保证变量对多有Thread的可见性
- 变量值被修改,Thread能立即感知这个值
- 在切换线程时候,变量可能被重定义,却来不及更新建议用 volatile,但是 volatile 并不能完美解决线程安全问题,只能做变量提前预判,此时得采用acomic或者syhnronized等其他方式
四.long和double
long 和 double 具有非原子性,不需要用考虑使用volatile修饰
五.原子性,可见性,有序性
- 原子性: 除了 long 和 double以外的基本数据类型都具有原子性
- 可见性: 当一个线程修改了这个值,其他线程能够立即感知,即 Main Memory 传递的方式具有可见性
- volatile 能保证新值能立即同步到 Main Memory,以及每次使用 从 Main Memory 立即 refresh.
- final 字段在constrctor方法 init 后,constrctor 没有把 "this" 引用传递出去(this 逃逸非常危险),那么在其他线程中就能看见 final 字段的值
- synchronized 变量在同步代码块,unlock之后,必须回到 Main Memory,执行完write操作
- 有序性:
- 本线程中所有操作都是有序的(Whithin-thread As-if-Serial Semantics)
- ThreadA watch ThreadB 都是无序的(指令重排,Main Memory与Working Memory 同步 延迟)
- volatile 禁止指令重排
- synchronized 一个变量 一个时刻只允许一条线程进行lock
六.happen-before
- 程序次序规则
在一个程序里面,按照程序代码顺序,书写在前面的操作happen-before发生书写在后面的操作 - 管程锁定规则
同一把锁,unlock happen-before lock 操作 - 线程中断原则
Thread.intercupt()方法检测到是否中断发生
- 传递性
Happens-Before 具有传递性,如果 A Happens-Before B,B Happens-Before C,则 A Happens-Before C
- 对象终结规则
对象 init happen-before finalize()
七.问题导致原因
- 缓存导致的可见性问题
- 切换线程导致的原子性问题
- 编译优化带来的有序性问题「指令重排」