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关键字可以禁止指令的重排序。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值