前言:
volatile关键字通过内存屏障和禁止重排序来优化实现
- 对volatile变量写操作时,会在写操作后加入一条store屏障指令,将本地内存中的共享变量值刷新到主内存
- 对volatile变量读操作时,会在读操作前加入一条load屏障指令,从主内存中读取共享变量
总之:每次对被线程访问时都强迫从主内存中读取该变量的值,而该变量发生变化时,又强迫线程将最新的值刷新回主内存,任何时候线程都能看到最新的值。
volatile关键字有可见性,并没有原子性,这也就是为什么它不用于计数,非要用的话,就要加上Atomic原子类。
它常用的两个功能:
1. 检查某个状态标记以判断是够退出循环
2. 双重检测模式即懒汉模式的单例模式中
一、标记状态
@Slf4j
public class volatileExample {
private volatile static boolean isRunning = true;
private static void shutdown(){
isRunning = false;
System.out.println("关闭了");
}
private static void isStart(String name){
System.out.println("start...");
System.out.println(name);
while(isRunning){}
System.out.println("end...");
}
public static void main(String[] args) throws InterruptedException{
new Thread(() -> {
isStart(Thread.currentThread().getName());
}).start();
Thread.sleep(100);
new Thread(() -> shutdown()).start();
}
}
可以看出,前者没有加volatile关键字,会一直从工作内存取值,导致循环等待,而加了之后强制性从主内存取值。
二、双重同步锁单例模式(懒汉模式)
public class Singleton {
// 私有构造函数
private Singleton() {
}
// 1、memory = allocate() 分配对象的内存空间
// 2、ctorInstance() 初始化对象
// 3、instance = memory 设置instance指向刚分配的内存
// 单例对象 volatile + 双重检测机制 -> 禁止指令重排
private volatile static Singleton instance = null;
// 静态的工厂方法
public static Singleton getInstance() {
if (instance == null) { // 双重检测机制 A
synchronized (Singleton.class) { // 同步锁
if (instance == null) { //B
instance = new Singleton();
}
}
}
return instance;
}
}
分析:
- 构造方法私有化,表示不能在外部实例化这个对象,即只能从内部去引用,这就是单例,从头至尾只有一个对象实例。
- 加volatile关键字,是为了防止指令重排,如果不加的话,由于有指令重排优化:
1、memory = allocate() 分配对象的内存空间
2、ctorInstance() 初始化对象
3、instance = memory 设置instance指向刚分配的内存
JVM和cpu优化,发生了指令重排
1、memory = allocate() 分配对象的内存空间2、instance = memory 设置instance指向刚分配的内存
3、ctorInstance() 初始化对象