volatile

volatile 有两个作用

1 线程内存可见性

2 指令排序

可见性原理:

线程在对Volatile变量执行写操作时强迫线程将最新的值刷新到主内存中,而在读操作时强迫从主内存中读入变量的值

 

 2 指令重排

使用了volatile修饰的变量,在对改变量进行读写的时候会添加屏障规则;

在讲指令重排之前先介绍内存屏障

内存屏障(memory barrier)是一个CPU指令。这条指令可以确保一些特定指令的执行顺序,相当于在上下两条指令之间添加一个障碍,阻止指令交换位置执行。

jvm提供了四种屏障规则:Load=>读,Store=>写

  1)LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
(2)StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
(3)LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
(4)StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。它的开销是四种屏障中最大的。在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能。

举个例子:

  

public class Test2 {
     T o = new T();
}

class T {
    int m = 2;
}

查看汇编代码

 主要关注5 9 12 三行代码

 5=》在内存中开辟一个空间用来给o对象使用,但是此时都是初始值,m=0;

 9=>调用T的构造方法,m=2;

 12=>讲o指向刚开辟的内存空间的地址

此时如果进行指令重排很可能是先执行12 然后在执行9。这就是为什么懒加载双重锁创建单例对象时 为什么要用volatile修饰了来禁止指令重排。

使用volatile修饰之后

public class Test2 {
   volatile  T o = new T();
}

class T {
    int m = 2;
}

相当于在12行上线加了StoreStore StoreRead指令,从而不会让12和9调换位置

原文地址:https://www.cnblogs.com/Tony100/p/12931692.html