禁止指令重排总结
volatile
实现了禁止指令重排优化,从而避免多线程环境下程序出现乱序执行的现象。
先了解一个概念,内存屏障(Memory Barrier) 又称为内存栅栏,是一个CPU 指令,它有两个作用:
1、保证特定操作的执行顺序。
2、保证某些变量的内存可见性(利用该特征实现 volatile 的内存可见性)
由于编译器和处理器都能执行指令重排优化。如果在指令间插入一条 Memmory Barrier 则会告诉编译器和 CPU ,不管什么指令都不能和这条 Memory Barrier 指令重排序,也就是说通过插入内存屏障禁止在内存屏障前后的指令执行重排优化。内存屏障另外一个作用是强制刷出各种 CPU 的缓存数据,因此任何 CPU 上的吓成都能读取到这些数据是最新版本
线程安全性获得保障(线程安全访问)
工作内存与主内存同步延迟现象导致的可见性问题
可以使用 synchronized 或者 volatile 关键在解决,他满都可以使一个线程修改后的变量立即对其他的线程可见。
对于指令重排序导致的可见性问题和有序性问题
可以利用 volatile 关键字解决,因为 volatile 的另外一个作用就是禁止重排序优化。
volatile 运用场景
你在那些地方见过 volatile
单例模式 DCL
无 volatile
关键字的单例 DCL
public class SingletonDemo { private static SingletonDemo singletonDemo; private SingletonDemo() { System.out.println(Thread.currentThread().getName() + "\t invoke SingletonDemo()"); } public static SingletonDemo getSingleton() { if (singletonDemo == null) { // 同步代码块加锁 synchronized (SingletonDemo.class) { if (singletonDemo == null) { singletonDemo = new SingletonDemo(); } } } return singletonDemo; } }
单例模式 volatile 分析
有 volatile
关键字的单例 DCL
public class SingletonDemo { private static volatile SingletonDemo singletonDemo; private SingletonDemo() { System.out.println(Thread.currentThread().getName() + "\t invoke SingletonDemo()"); } public static SingletonDemo getSingleton() { if (singletonDemo == null) { // 同步代码块加锁 synchronized (SingletonDemo.class) { if (singletonDemo == null) { singletonDemo = new SingletonDemo(); } } } return singletonDemo; } }
单例模式总结
DCL 双端检锁, 机制下不一定是线程安全的,原因是有指令重排的存在,加入volatile 可以禁止指令重排
原因在于某一个线程执行到第一次检测,读取到了 instance 不为 null 时,instance 的引用对象 可能没有完全完成初始化。
模拟代码:
instance = new SingletonDemo() memory = allocate() //1. 分配内存空间 instance(memory) //2. 初始化对象 instance = memory //3.设置 instance 执行刚才分配的内存地址,此时 instance!= null
步骤 2 和步骤 3 不存在数据依赖关系,而且无论重排前还是重排后的执行结果在但线程中并没有发生改变,因此这种重排优化是优化是允许的
instance = new SingletonDemo() memory = allocate() //1. 分配内存空间 instance = memory //3.设置 instance 执行刚才分配的内存地址,此时 instance!= null 但是对象还没有被初始化完成! instance(memory) //2. 初始化对象
但是指令重排只会保证串行语句的执行的一致性(单线程),但是并不会关心多线程间的语义一致性。
所以当一条线程访问 instnce 不为 null 时,由于 instance 实例尾部已经初始化完成,也就造成了线程安全问题。