一、 背景 —— 什么是 JMM
JMM 是 Java 内存模型的缩写,其将 Java 运行的底层内存模型抽象成了 主存 和 工作内存。 其中, 主存就是线程共享的内存, 工作内存就是线程所私有的内存 。
二、什么是可见性
可见性是指放在主存中的内容所有线程都是可见的 。
但是由于 JIT (即时编译器)的存在,有时我们放在主存中内容,会被其移动到工作内存给某个线程私有 。(嘤嘤嘤, 不能怪 JIT 鸭 ~ 它也是为了优化程序执行的效率,它也不知道不能移鸭 ~)
三、引出 volitale
1. volatile 用来做什么
volatile 用于保证我们某个变量的可见性,使其一直存放在主存中,不被移动到某个线程的私有工作内存中。 (synchronized 也能实现可见性,但是麻烦,没有 volatile 简洁,而且更加重量级,会降低效率)
2. 举个例子,怎么使用
volatile 可以用来修饰 :
- 成员变量
- 静态成员变量
使用实例
public class TestVolatile {
volatile static boolean use = true;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while (use){
// 执行相应操作
}
});
thread.start();
// 主线程休眠一秒
sleep(1000);
use = false;
}
}
3. volatile 可以保证原子性嘛
不能,它只能保证可见性,不能保证原子性 。
四、 volatile 原理 - JDK 1.5 之后
内存屏障技术, Memory Barrier
- 写指令,加入写屏障
- 读指令,加入读屏障
1. 保证可见性
- 写屏障保证在该屏障之前,对共享变量的改动,都同步到主存中
- 读屏障保证在该屏障之后,对共享变量的读取,加载的是主存中的最新数据
2. 保证有序性
- 写屏障确保指令重排时,不会将写屏障之前的代码排在写屏障之后
- 读屏障确保指令重排时,不能将读屏障之前的代码排在读屏障之后
3. 解决 dcl 问题 (double-checked locking)
public class Singleton {
private Singleton() {
}
private static Singleton INSTANCE = null;
public static Singleton getINSTANCE(){
if(INSTANCE == null){
synchronized (Singleton.class) {
if(INSTANCE == null) {
// 会因为指令重排序出现问题
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
解决 dcl 问题
为共享变量加上 volatile 关键字,从而避免指令重排序的问题 。 (jvm 可能对指令进行重排序,重排序可以会在多线程并发时出现问题)
文章评论