volatile关键字的作用

(1)保证内存的可见性

  基本概念:可见性指的是线程之间的可见性,一个线程修改的状态对另一个线程是可见的。一个线程修改的结果,另一个线程能够马上就看到。

  原理:废volatile变量进行读写的时候,每个线程先从主内存拷贝变量到CPU缓存中,如果计算机有多个CPU,每个线程可能会拷贝到不同的CPU cache中。但是volatile变量不会被缓存在寄存器或者是对其他处理器不可见的地方。保证每次读写变量操作都从主内存中读,跳过CPU cache这一步,当一个线程修改这个变量的值,新值对于其他线程而言是立即得知的。

(2)禁止指令重排

  基本概念:指令重排是JVM为了优化指令,提高程序运行效率,在不影响单线程程序执行结果的前提下,尽可能的提高并行度。指令重排包括编译器重排序和运行时重排序。

  volatile变量禁止指令重排序。针对volatile修饰的变量,在读写操作指令前后会插入内存屏障,指令重排序时不能把后面的指令重排序到内存屏障。

虽然代码语句的定义顺序为1->2->3,但是计算顺序1->2->3与2->1->3对结果并无影响,所以编译时和运行时可以根据需要对1、2语句进行重排序。

 

禁止重排的原理:

volatile关键字提供内存屏障的方式来防止指令被重排,编译器在生成字节码文件时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。

JVM内存屏障插入策略:

每个volatile写操作的前面插入一个StoreStore屏障;

在每个volatile写操作的后面插入一个StoreLoad屏障;

在每个volatile读操作的后面插入一个LoadLoad屏障;

在每个volatile读操作的后面插入一个LoadStore屏障。

 

指令重排在双重锁单例模式中的影响

instance= new Singleton()并不是一个原子操作,其实际上可以抽象为下面几条JVM指令:

上面操作2依赖于操作1,但是操作3并不依赖于操作2。所以JVM是可以针对它们进行指令的优化重排序的,经过重排序后如下:

指令重排之后,instance指向分配好的内存放在了前面,而这段内存的初始化被排在了后面。在线程A执行这段赋值语句,在初始化分配对象之前就已经将其赋值给instance引用,恰好另一个线程进入方法判断instance引用不为null,然后就将其返回使用,导致出错。

解决办法:用volatile关键字修饰instance变量,使得instance在读、写操作前后都会插入内存屏障,避免重排序。

 

适用场景:

(1)volatile是轻量级同步机制。在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,是一种比synchronized关键字更轻量级的同步机制。

(2)volatile无法同时保证内存可见性和原子性。加锁机制既可以确保可见性又可以确保原子性,而volatile变量只能确保可见性。

(3)volatile不能修饰写入操作依赖当前值的变量。声明为volatile的简单变量如果当前值与该变量以前的值相关,那么volatile关键字不起作用,也就是说如下的表达式都不是原子操作:“count++”、“count = count+1”。

(4)当要访问的变量已在synchronized代码块中,或者为常量时,没必要使用volatile;

(5)volatile屏蔽掉了JVM中必要的代码优化,所以在效率上比较低,因此一定在必要时才使用此关键字。

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值