Java 内存模型即 Java Memory Model,简称 JMM。JMM 是一个抽象的概念,作用于 JVM 和底层硬件之间,屏蔽了不同硬件模型带来的差异,为开发者提供了统一的使用接口。一句话:JMM 就是JVM内存的实现规范。
Java 内存模型
JMM 的八大原子操作
- read(读取):从主内存读取数据
- load(载入):将主内存的数据写入工作内存
- use(使用):从工作内存读取数据
- assign(赋值):将值赋值到工作内存
- store(存储):将工作内存的数据写入主内存
- write(写入):将 store 的变量赋值到主内存的变量
- lock(锁定):将主内存变量加锁,标识为线程独占状态
- unlock(解锁):将主内存变量解锁,解锁后其他线程可以锁定该变量
MESI 缓存一致性协议(CPU级别)
MESI 协议提供了一种方式,成功的解决了缓存一致性问题。对于缓存中的每一行,都设置一个状态位,一共有四种状态:
- Modified:表示缓存行仅存在于当前的缓存中,并且已经被更改。在该缓存行写回到主存之前,任何其他CPU都不能读取该缓存行的内容。
- Exclusive:表示缓存行仅存在于当前的缓存中,并且未被修改。如果有其他CPU读取该行,则转移到Shared状态;如果修改该行,则转移到Modified状态。
- Shared:表示有多个CPU共享该缓存行,且内容未被修改。
- Invalid:表示该缓存无效。
实现缓存一致性的流程
- 由于硬件层面基本都实现了MESI协议,变量在线程内被修改后,在写回工作主内存之前必定经过总线
- 基于CPU总线嗅探机制,CPU在检测到感兴趣(监听)的变量时,将其他工作线程具有相同地址的变量置为失效
- 并且在write 完成之前对变量进行 lock 操作,其他线程在此期间不能读取该变量,直至write完成
JMM 模式下的线程通信
- 线程之间没有直接的通信道路,只能通过主内存间接进行通信
- A 线程对 x 进行修改之后,先同步回主内存,然后 B 线程在通过主线程获取 A 线程修改过的新值。
as-if-serial语义
as-if-serial 是为了优化,可以将代码的执行顺序进行重排序,但必须保证重排序后的执行结果与原先的结果保持一致。Java编译器、运行时和处理器都会保证单线程下的as-if-serial语义。
内存屏障
内存屏障(Memory Barrier,或有时叫做内存栅栏,Memory Fence)是一种CPU指令,用于控制特定条件下的重排序和内存可见性问题。Java编译器也会根据内存屏障的规则禁止重排序。
- 硬件层的内存屏障分为两种:Load Barrier 和 Store Barrier即读屏障和写屏障。
- 内存屏障有两个作用:
- 阻止屏障两侧的指令重排序;
- 强制把写缓冲区/高速缓存中的脏数据等写回主内存,让缓存中相应的数据失效。
- java的内存屏障通常所谓的四种
- LoadLoad屏障:Load1; LoadLoad; Load2; 在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
- StoreStore屏障:Store1; StoreStore; Store2; 在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
- LoadStore屏障:Load1; LoadStore; Store2; 在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
- StoreStore屏障:Store1; StoreLoad; Load2; 在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。
volatile语义中的内存屏障
- 在每个volatile写操作前插入StoreStore屏障,在写操作后插入StoreLoad屏障;
- 在每个volatile读操作前插入LoadLoad屏障,在读操作后插入LoadStore屏障。
final语义中的内存屏障
- 新建对象过程中,构造体中对final域的初始化写入和这个对象赋值给其他引用变量,这两个操作不能重排序;(写入完毕后才能引用和读取)
- 初次读包含final域的对象引用和读取这个final域,这两个操作不能重排序。(先赋值引用)
happens-before原则
happens-before原则定义如下:
- 如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。
- 两个操作之间存在happens-before关系,并不意味着一定要按照happens-before原则制定的顺序来执行。如果重排序之后的执行结果与按照happens-before关系来执行的结果一致,那么这种重排序并不非法。
happens-before原则规则:
- 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作;
- 锁定规则:一个unLock操作先行发生于后面对同一个锁额lock操作;
- volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作;
- 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C;
- 线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作;
- 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生;
- 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行;
- 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始;