Volotile
概念
意思为易变的; 无定性的; 无常性的; 可能急剧波动的; 不稳定的;等。
保证线程可见性。(底层靠CPU的缓存一致性协议MESI保证)
禁止指令重排序。(依靠内存屏障实现)
不能保证原子性,即不能替换synchronized。
线程可见性
java中存在堆内存,堆内存对所有线程共享,此外,每个线程都有自己的专属区域(工作内存),当一个线程访问一个值时,如下面的例子的running,会将此值复制到自己的工作空间里面,对此值的改变是先在自己的工作空间里面改变,会立刻写回共享空间,但是其他线程什么时候再读就不确定。即一个线程对某个值的改,并不会立刻反应到其他线程里,这就是线程之间不可见。
//测试保证线程可见性demo
public class Test {
/*volatile */ boolean running = true;
void m1(){
System.out.println("开始调用");
while (running){
}
System.out.println("结束调用");
}
public static void main(String[] args) {
Test test = new Test();
new Thread(test::m1).start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
test.running = false; //执行结果:m1()方法并不会退出;把volatile打开后就能退出,即主线程对running的修改,其他线程也立刻能知道,这就是保证线程可见性。
//此句证实线程对一个值的改变是先在自己的工作空间里面改变,会立刻写回共享空间的。
new Thread(() -> System.out.println(test.running)).start();
}
/* 输出结果:
开始调用
false
*/
/* volatile打开后,输出结果:
开始调用
结束调用
false
*/
}
禁止指令重排序
CPU层面上,现代的CPU为了效率,在执行指令时,并不是一条执行完再执行下一条,而是并发的执行,第一条没执行完,会直接执行第二条。 其也有重排序。
虚拟机层面上,编译器(类似java里的),编译完源码后,可能不一定按编写的代码顺序进行,而可能是根据优化规则,对指令进行重排序。
此外还有读写屏障的概念。
//懒汉式单例模型是否还需要加volatile?
public class Test {
//本类内部创建对象实例
//在双重检查机制下的懒汉式单例,是否还需要加volatile?
private static /*volatile*/ Test instance = null;
//构造方法私有化,外部不能new
private Test() {
}
//提供一个公有的静态方法,返回实例对象
public static Test getInstance() {
if (instance == null) { //第一重检查,避免无效的synchronized
synchronized (Test.class){
if (instance == null) { //第二重检查,单例必要的
instance = new Test();
}
}
}
return instance;
}
}
//instance = new Test();编译器编译完后,它的指令分成三步:1.给对象申请内存,2.对对象的成员变量初始化,3.把这个块内存赋值给instance(instance在栈内存里)。
//如果出现指令重排序,3与2的执行顺序变换,那么在这种单例下不加volatile,就有可能出现,线程拿到的对象里面的成员变量是未初始化之前的默认值(如int就是0),这种情况可能出现在超高超高并发下。
DCL单例指的是具有双重检查的单例模式:
https://www.cnblogs.com/codingmengmeng/p/9846131.html
volatile如何解决指令重排
1.代码层,关键字volatile
2.字节码层,ACC_VOLATILE
3.JVM的内存屏障(遵循JSR)
StoreStoreBarrier -> vloatile 写操作 -> StoreLoadBarrier
LoadLoadBarrier -> volatile 读操作 -> LoadStoreBarrier
4.hostspot实现
bytecodeinterpreter.cpp -> orderAccess fence() -> lock addl
CPU是有sfence mfence lfence等系统原语支持内存屏障的(有些CPU不支持),只是hostspoty用的是lock(锁总线)
内存屏障
屏障两边的指令不能重排,保证有序。
JSR内存屏障
LoadLoad,StoreStore,LoadStore,StoreLoad四种
其中Load代表读,Store代表写。