5、共享模型之内存管理
5.1 Java 内存模型
JMM 即 Java Memory Model,它定义了主存、工作内存抽象概念,底层对应着 CPU 寄存器、缓存、硬件内存、CPU 指令优化等
JMM 作用:屏蔽各种硬件和操作系统的内存访问差异,实现让 Java 程序在各种平台下都能达到一致的内存访问效果,
规定了线程和内存之间的一些关系
运作流程
概念区分
内存交互
三大特性
可见性、原子性
5.2 可见性
含义:
是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值
由于缓存的存在,线程持有的是共享变量的副本,无法感知其他线程对于共享变量的更改,导致读取的值不是最新的。但是 final 修饰的变量是不可变的,就算有缓存,也不会存在不可见的问题
main 线程对 run 变量的修改对于 t 线程不可见,导致了 t 线程无法停止:
1. 初始状态, t 线程刚开始从主内存读取了 run 的值到工作内存。
因为 t 线程要频繁从主内存中读取 run 的值,JIT 编译器会将 run 的值缓存至自己工作内存中的高速缓存中,减少对主存中 run 的访问,提高效率
1 秒之后,main 线程修改了 run 的值,并同步至主存,而 t 是从自己工作内存中的高速缓存中读取这个变量的值,结果永远是旧值
1、CPU 缓存结构原理
2、CPU 缓存读
3. CPU 缓存一致性
解决方法
5.3 有序性
说明:
在本线程内观察,所有操作都是有序的;在一个线程观察另一个线程,所有操作都是无序的,无序是因为发生了指令重排序
CPU 的基本工作是执行存储的指令序列,即程序,程序的执行过程实际上是不断地取出指令、分析指令、执行指令的过程,为了提高性能,编译器和处理器会对指令重排,一般分为以下三种:
指令级并行原理
解决方法
volatile 修饰的变量,可以禁用指令重排
原理之 volatile
1. 如何保证可见性
2. 如何保证有序性
3. double-checked locking 问题
happens-before原则
happens-before 规定了对共享变量的写操作对其它线程的读操作可见,它是可见性与有序性的一套规则总结
抛开以下 happens-before 规则,JMM 并不能保证一个线程对共享变量的写,对于其它线程对该共享变量的读可见
都是针对成员变量和静态成员变量
1 程序顺序规则:
一个线程中的每个操作,happens-before于该线程中的任意后续操作。
2 监视器锁规则:
对于一个锁的解锁,happens-before之后是对这个锁的加锁。
3 volatile变量规则:
对一个volatile域的写,happens-before之后是对volatile域的读。
4 传递性:
如果A happens-before B,B happens-before C,那么A happens-before C。
5 start()规则:
如果线程A执行操作ThreadB•start(),那么线程A的ThreadB.start()happens-before线程B中的任意操作。
6 join()规则
如果线程A执行操作ThreadB.join()并且成功返回,那么线程B中的任意操作于happens-before线程A从ThreadB.join()操作成功返回。
注意⚠️ 两个操作之间有happens-before的关系,不一定是A操作在B操作之前执行。如果重排序之后执行的结果,与按照happens-before关系来执行的结果一致,那么这种重排序并不非法。
简单来说如果前一个操作happens-before后一个操作,只需要前一个操作执行的结果对后一个操作可见,而且前一个操作的顺序在第二个操作之前。这是JMM对程序员做出的保证。