Java 虚拟机规范中定义一种Java 内存模型(Java Memory Model, JMM)来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让 Java 程序在各种平台下都能达到一致的内存访问效果。
1. 主内存与工作内存
Java 内存模型的主要目标是定义程序中各个变量的访问规则。此处的变量包括实例字段、静态字段和构造数组对象的元素,但不包括线程私有的局部变量与方法参数等(因为它们不是线程共享的)。
Java 内存模型规定
-
所有的变量都存储在主内存(Main Memory)中。
-
每条线程还有自己的工作内存(Working Memory),线程的工作内存保存被该线程使用到的变量在主内存中的副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变量。
-
不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量的传递均需要通过主内存来完成。
线程、主内存、工作内存三者的交互关系如图所示。
线程、主内存、工作内存三者的交互关系
2. 内存间交互操作
Java 内存模型定义了 8 种操作来完成主内存与工作内存之间的具体交互协议。虚拟机实现时必须保证每一种操作都是原子的、不可再分的。这 8 种操作分别为:lock、unlock、read、load、use、assign、store、write。
3. volatile 型变量的特殊规则
关键字 volatile 是 Java 虚拟机提供的最轻量级的同步机制。当一个变量定义为 volatile 之后,它将具备两种特性。
1)保证此变量对所有线程的可见性。这里的可见性是指当一个线程修改了这个变量的值,新值对于其他线程来说是立即可以得知的。
由于 volatile 变量只能保证可见性,在不符合以下两条规则的运算场景中,我们仍然要通过加锁来保证原子性。
-
运算结果并不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值。
-
变量不需要与其他的状态变量共同参与不变约束。
2)禁止对此变量指令重排序优化。指令重排是指 CPU 采用了允许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理。重排的结果看起来依然是有序的。而 volatile 变量的修改会同步到内存中,意味着之前所有的操作都已经执行完成,同时,引起其他 CPU 的 Cache 无效化,使得其他 CPU 立即可见。从而避免由指令重排导致的错误。使用 volatile 关键字可以安全地使用 DCL(双锁检测)来实现单例模式。
public class Singleton {
private volatile static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
public static void main(String[] args) {
Singleton.getInstance();
}
}
4. 原子性、可见性与有序性
-
原子性(Atomicity): 基本数据类型的读写具有原子性。更大范围的原子性保证需要用到 synchronized 关键字。
-
可见性(Visibility): 可见性是指当一个线程修改了共享变量的值,其他线程能够立即得知这个修改。除了 volatile 之外,Java 还有两个关键字能实现可见性,即 synchronized 和 final。同步块的可见性是由于对一个变量执行unlock操作之前,必须先把此变量同步回主存中。而 final 关键字的可见性是指:被 final 修饰的字段一旦初始化完成,其他线程就能够看见 final 字段的值。
-
有序性(Ordering): Java 程序中天然的有序性可以总结为:如果在本线程内观察,所有操作都是有序的;如果在一个线程中观察另一个线程,所有操作都是无序的。前半句指“线程内表现为串行语义”(Within-Thread As-If-Serial Semantics),后半句指“指令重排”现象和“工作内存与主内存同步延迟”现象。
Java 语言提供了 volatile 和 synchronized 两个关键字来保证线程之间操作的有序性。volatile 关键字本身包含禁止指令重排序的语义,而 synchronized 则是由于“一个变量在同一个时刻只允许一条线程对其进行 lock 操作”这条规则保证了串行地进入。
5. 先行发生原则
先行发生原则是用来判断数据是否存在竞争、线程是否安全的主要依据。如果两个操作之间的关系不包含在下面的规则之中,并且无法从下面的规则推导出来,它们就没有顺序性保障,虚拟机可以对它们随意的进行重排序。
- 程序次序规则
- 管程锁定规则
- volatile 变量规则
- 线程启动规则
- 线程终止规则
- 线程中断规则
- 对象终结规则
- 传递性
有这样一个结论,时间先后顺序与先行发生原则之间基本没有太大关系,所以我们衡量并发安全问题的时候不要受到时间顺序的干扰,一切必须以先行发生原则为准。
网友评论