volatile
volatile关键字在Java面试中几乎是必考题 单例模式的双重检查模式(DCL)一般会引申到synchronized关键字和volatile关键字
volatile关键字了解吗?它保证了什么特性?用什么方式禁止指令重排的吗?
1)volatile保证了什么特性
用什么方式禁止指令重排的吗?
内存屏障:
-
读屏障(Load Barrier):在读指令前插入Load Barrier,可以让高速缓存中的数据失效,强制重新从主内存加载数据,保证读取的是最新数据。 -
写屏障(Store Barrier):在写指令后插入Store Barrier,能让写入缓存中的最新数据更新写入主内存,保证写入的数据立刻对其他线程可见。
- 在工作内存中,每次使用V(volatile变量)前都必须先从主内存刷新最新的值,用于保证能看见其他线程对变量V所做的修改。
- 在工作内存中,每次修改V后都必须立刻同步回主内存中,用于保证其他线程可以看到自己对变量V所做的修改。
2)volatile导致哪条代码需要指令重排?
instance = new Singleton();
会被编译器编译成如下JVM指令:
- memory = allocate(); //分配对象的内存空间
- ctorInstance(memory); //初始化对象
- instance = memory; //设置instance指向刚分配的内存空间
但是这些指令排序并非一成不变,有可能会经过JVM和CPU的优化,指令重排为如下顺序:
- memory = allocate(); //分配对象的内存空间
- instance = memory; //设置instance指向刚分配的内存空间
- ctorInstance(memory); //初始化对象
当线程A执行完1,3,时,instance对象还未完成初始化,但已经不再指向null。此时如果线程B抢占到CPU资源,执行 if(instance == null)的结果会是false,从而返回一个没有初始化完成的instance对象。
实例化对象实际会分为3个步骤:
- 分配内存空间
- 初始化对象
- 将对象指向刚分配的内存空间
但有的编译器由于性能的原因,可能会将第二步和第三步进行重排序,顺序就成了:
- 分配内存空间
- 将对象指向刚分配的内存空间
- 初始化对象
使用了volatile关键字之后,重排序被禁止,所有的写操作(write)都发生在读操作(read)之前。
|