java中的volatile关键字
java中的volatile关键字
volatile修饰的变量具有两个性质:线程可见性,禁止重排序。
一、测试volatile的多线程可见性
1.volatile的多线程可见性测试:
public class TestVolatile{
//while是否循环的标志(注:不加volatile时多线程不可见)
/*volatile*/ boolean running = true;
void m(){
//线程启动输出
System.out.println("m start");
//running值未变为false就会一直循环(看在主线程修改running值后,其他线程是否会跳出循环(是否可见),测试时线程结束的标志!)
while(running){
}
//如果跳出循环输出
System.out.println("m stop");
}
public static void main(String[] args){
TestVolatile t = new TestVolatile();
//启动线程,执行m方法,线程名为“t1”
new Thread(t::m,"t1").start();
try{
//主线程暂停1秒(目的:看1秒后是否能使"t1"跳出循环)
TimeUnit.SECONDS.sleep(1);
}catch(InterruptedException e){
e.printStackTrace();
}
//把running值变为false,看能否呗“t1”看到
t.running = false;
}
}
2.测试方法及结论:
-
先注释掉volatile,发现不会输出“m stop”,此时说明running变量多线程不可见。
-
解开注释volatile,发现会输出“m stop”,此时说明volatile修饰的running变量多线程可见。
3.原理图:
不加volatile时的流程图;
二、禁止重排序
在指令中间加内存屏障,来禁止重排序。
hotspot中实现:
lock作用:用于在多处理器中执行指令时对共享内存的独占使用。(直接锁住的总线,让其他线程不能访问)
它的作用是能够将当前处理器对应缓存的内存刷新到新内存,并使其他处理器对应的缓存失效。另外还提供了有序的指令无法越过这个内存屏障的作用。
拓展::
jvm级别的内存屏障
1.JSR内存屏障(Load:读,Store:写)
-
LoadLoad屏障
对于这样的语句Load1;LoadLoad;Load2;
在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。 -
StoreStore屏障
对于这样的语句Store1;StoreStore;Store2;
在Store2及后续写入操作执行前,保证Store1的写入操作对其他处理器可见。 -
LoadStore屏障
对于这样的语句Load1;LoadStore;Store2;
在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。 -
StoreLoad屏障
对于这样的语句Store1;StoreLoad;Load2;
在Load2及后续所有读取操作执行前,保证Store1的写入操作对其他处理器可见。
2.jvm规范要求的屏障(根据JSR内存屏障):
3.JVM 规定重排序必须遵守的规则(happen-before原则)
顺带一说:
as if serial(看起来是顺序执行的):不管如何重排序,单线程执行结果不会改变。
e.g. a=1;b=2;不管怎么重排,都对结果没影响。
x86 CPU内存屏障
自己的汇编指令直接就带内存屏障。
sfence:在sfence指令前的写操作当必须在sfence指令后的写操作前完成。
lfence:在lfence指令前的读操作当必须在lfence指令后的读操作前完成。
mfence:在mfence指令前的读写操作当必须在mfence指令后的读写操作前完成。
美团50w的面试题:DCL单例(双重检查锁)要不要加volatile?
是需要的,回答此问题的前提基础:
-
1.CPU指令的乱序执行
乱序执行:后面的语句先执行。
CPU在进行读等待的同时执行指令,是CPU乱序的根源。乱序执行是为了提高效率。 -
2.对象创建的过程
创建对象的汇编码:
第1条 new:申请一块内存空间,m的值为默认值(0) ——> 半初始化状态
第2条 invokespecial :调用init这个特殊方法给m赋值8
第3条 astore_1:建立关联。(t对象的引用,此时t ! = null)
问题的答案:要加volatile,因为指令的执行可能会重排序,先建立关联而后赋值。此时其他线程可能会读取倒该线程半初始化的对象,此时哪个线程会读取到m=0,就会出现问题(此种情况发生的可能性极低)。此时加volatile关键字可以禁止指令的重排序。