Java内存模型
Java内存模型Java Memory Model,简称JMM,本身是一种抽象的概念并不真实存在它仅仅描述的是一组约定或规范,通过这组规范定义了程序中(尤其是多线程)各个变量的读写访问方式并决定一个线程对共享变量的写入何时以及如何变成对另一个线程可见,关键技术点都是围绕多线程的原子性、可见性和有序性展开的。
JMM规范下,三大特性
- 原子性
- 可见性
- 有序性
原子性
指一个操作是不可中断的,即多线程环境下,操作不能被其他线程干扰。
可见性
-
Java中普通的共享变量不保证可见性,因为数据修改被写入内存的时机是不确定的,多线程并发下很可能出现"脏读"。
-
每个线程都有自己的工作内存,线程自己的工作内存中保存了该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作(读取,赋值)都必需在线程自己的工作内存中进行,而不能够直接读写主内存中的变量。
有序性
-
对于一个线程的执行代码而言,我们总是习惯性认为代码的执行总是从上到下,有序执行。 但为了提供性能,编译器和处理器通常会对指令序列进行重新排序。
-
指令重排可以保证串行语义一致,但没有义务保证多线程间的语义也一致,即可能产生"脏读"。
总结
- 我们定义的所有共享变量都储存在物理主内存中
- 每个线程都有自己独立的工作内存,里面保存该线程使用到的变量的副本(主内存中该变量的一份拷贝)
- 线程对共享变量所有的操作都必须先在线程自己的工作内存中进行后写回主内存,不能直接从主内存中读写(不能越级)
- 不同线程之间也无法直接访问其他线程的工作内存中的变量,线程间变量值的传递需要通过主内存来进行(同级不能相互访问)
volatile
关键字,保证不同线程对这个变量进行操作时的可见性,即变量一旦改变所有线程立即可见,指令禁重排,不能保证原子性。
- 可见性
- 有序性
volatile凭什么可以保证可见性和有序性???
内存屏障 (Memory Barriers / Fences),是一种屏障指令,它使得CPU或编译器对屏障指令的前和后所发出的内存操作执行一个排序的约束。也叫内存栅栏或栅栏指令。
- 阻止屏障两边的指令重排序
- 写数据时加入屏障,强制将线程私有工作内存的数据刷回主物理内存
- 读数据时加入屏障,线程私有工作内存的数据失效,重新到主物理内存中获取最新数据
屏障类型
写屏障
-
在每个 volatile 写操作的前⾯插⼊⼀个 StoreStore 屏障
-
在每个 volatile 写操作的后⾯插⼊⼀个 StoreLoad 屏障
读屏障
- 在每个 volatile 读操作的后⾯插⼊⼀个 LoadLoad 屏障
- 在每个 volatile 读操作的后⾯插⼊⼀个 LoadStore 屏障
volatile可见性
volatile关键字保证可见性,意味着∶
-
对一个volatile修饰的变量进行读操作的话,总是能够读到这个变量的最新的值,也就是这个变量最后被修改的值
-
一个线程修改了volatile修饰的变量的值的时候,那么这个变量的新的值,会立即刷新回到主内存中
-
一个线程去读取 volatile 修饰的变量的值的时候,该变量在工作内存中的数据无效,需要重新到主内存去读取最新的数据
volatile有序性
- volatile 写之前的操作,都禁止重排序到volatile之后
- volatile读之后的操作,都禁止重排序到volatile之前
- volatile 写之后的操作,都禁止重排序到volatile之前