欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

java中的volatile关键字

程序员文章站 2024-03-23 12:10:46
...

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时的流程图;java中的volatile关键字

二、禁止重排序

在指令中间加内存屏障,来禁止重排序。

hotspot中实现:

java中的volatile关键字

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内存屏障):

java中的volatile关键字

3.JVM 规定重排序必须遵守的规则(happen-before原则)

java中的volatile关键字

顺带一说:

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.对象创建的过程
    创建对象的汇编码:

java中的volatile关键字

第1条 new:申请一块内存空间,m的值为默认值(0) ——> 半初始化状态
第2条 invokespecial :调用init这个特殊方法给m赋值8

第3条 astore_1:建立关联。(t对象的引用,此时t ! = null)

问题的答案:要加volatile,因为指令的执行可能会重排序,先建立关联而后赋值。此时其他线程可能会读取倒该线程半初始化的对象,此时哪个线程会读取到m=0,就会出现问题(此种情况发生的可能性极低)。此时加volatile关键字可以禁止指令的重排序。

相关标签: Java