JMM——Java内存模型
JMM即Java内存模型(Java memory model),在JSR133里指出了JMM是用来定义一个 「一致的、跨平台」
的内存模型,是缓存一致性协议,用来定义数据读写的规则。
内存可见性
在Java中,不同线程拥有各自的私有 「工作内存」
,当线程需要读取或修改某个变量时,不能直接去操作 「主内存」
中的变量,而是需要将这个变量的读取到线程的 「工作内存」
的 「变量副本」
中,当该线程修改其变量副本的值后, 「其它线程并不能立刻读取到新值」
,需要将修改后的值 「刷新到主内存中」
,其它线程才能 「从主内存读取到修改后的值」
。
指令重排序
在执行程序时为了提高性能,编译器和处理器常常会对指令做重排序,指令重排序使得代码在 「多线程」
执行时会出现一些问题。
其中最著名的案例便是在 「初始化单例」
时由于 「可见性」
和 「重排序」
导致的错误。
单例模式
public class Singleton {
private static Singleton singleton;
private Singleton() {
}
public static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
以上代码是经典的 「懒汉式」
单例实现,但在多线程的情况下,多个线程有可能会同时进入 if (singleton == null)
,从而执行了多次 singleton = new Singleton()
,从而破坏单例。
public class Singleton {
private static Singleton singleton;
private Singleton() {
}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
以上代码在检测到 singleton
为null后,会在同步块中再次判断,自以为可以保证同一时间只有一个线程可以初始化单例,但仍然存在问题。原因就是Java中 singleton = new Singleton()
语句并不是一个 「原子指令」
,而是由三步组成:
- 为对象分配内存
- 初始化对象
- 将对象的内存地址赋给引用
但是当经过 「指令重排序」
后,会变成:
- 为对象分配内存
- 将对象的内存地址赋给引用(会使得singleton != null)
- 初始化对象
所以就存在一种情况,当线程A已经将内存地址赋给引用时,但 「实例对象并没有完全初始化」
,同时线程B判断 singleton
已经不为null,就会导致B线程 「访问到未初始化的变量」
从而产生错误。
public class Singleton {
private static volatile Singleton singleton;
private Singleton() {
}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
以上代码对 singleton
变量添加了 volatile
修饰,可以阻止 「局部指令重排序」
。
那么为什么volatile可以保证变量的可见性和阻止指令重排序?
volatile
- 规定线程每次修改变量副本后立刻同步到主内存中,用于保证其它线程可以看到自己对变量的修改
- 规定当线程使用变量前,先从主内存中刷新最新的值到工作内存,用于保证能看见其它线程对变量修改的最新值
-
为了实现可见性内存语义,编译器在生成字节码时,会在指令序列中插入 「内存屏障」
来 「防止指令重排序」
。
-
volatile只能保证基本类型变量的内存可见性,对于引用类型,无法保证引用所指向的 「实际对象内部数据」
的内存可见性。关于引用变量类型详见:Java的数据类型。 -
volilate只能解决共享对象的可见性,不能保证原子性:假设两个线程同时在做x++,在线程A修改共享变量从0到1的同时,线程B 「已经正在使用」
值为0的变量,所以这时候 「可见性已经无法发挥作用」
,线程B将其修改为1,所以最后结果并不是2而是1。
感谢您阅读本文,您的关注与点赞是对我最大的支持!