volatile关键字的作用_volatile的作用及正确的使用模式

volatile

先从基础的知识说起吧,这样也有个来龙去脉。

我们都知道,程序运行后,程序的数据都会被从磁盘加载到内存里面(主存)

7823298591b2a6dd7ae51afca5e3adf0.png

而当局部的指令被执行的时候,内存中的数据会被加载到更加靠近CPU的各级缓存,以及寄存器中。

f9d1f98ada92760626fbdb49c1cebb27.png

当一个多线程程序执行在一个多核心的机器上时,就会出现真正的并行情况,每个线程都独立的运行在一个CPU上,每个CPU都有属于自己独立的周边缓存。

那么此时,一个变量被两个线程操作,从内存复制到CPU缓存时,就可能出现两份了,这个时候就会出现问题,比如说简单的自增操作,就会变成,你加你的,我加我的,这样运行的效果就会偏离预期。线程1在CPU1上只看得见自己的缓存变量,线程2在CPU2上,也只看得见自己的缓存变量,它们都认为这是正确的、唯一的变量。这样也就导致程序运行结果偏离了预期。

volatile关键字就是用来处理这种可见性的问题。

当一个变量被标记为volatile的时候,这个变量将被放在主存里面,而不是CPU的缓存里面。当这个变量被读取的时候,是从主存读取,当这个变量被写入的时候,是写入到主存。

总所周知,内存肯定是比CPU缓存的读写速度要慢的。

那,是不是就意味着读写volatile的变量,效率会比非volatile的变量低呢

为了验证这个问题,我写了一个简单的程序,分别有3个变量,分别是成员变量,volatile修饰的成员变量,和static静态变量。

对它们进行1000000000次自增,然后记录下完成时间。

以上的操作执行10次,取平均值。

最终得到了以下的结果:

avg-------------- 
normal: 32.4 
volatile: 5828.3 
static: 43.8 

看来读写volatile变量,确实要比普通的变量要慢,但也是数量级非常大的时候,才会非常明显。

下面是我做的实验,右边比左边逐渐少一个量级,相对于数量级增长,volatile关键字修饰的变量读写耗时有了等比的线性增长。

而普通的成员变量和静态变量就没有这样的现象了,可见CPU缓存对性能的巨大提升。

acfeab6a8bf4b936621e572565f1cd4b.png

那么,使用volatile关键字可以让变量总是读写于主存,是不是就可以用它来避免多线程读写同一个变量导致的竞争问题呢?

答案是不能

因为,volatile关键字只是保证变量的可见性,而没有保证操作的原子性,因此,只使用这个关键字无法保证操作的原子性。

为了验证一下这个问题,我也写了一个小程序来测试。

有两个变量a1,a2,a1是普通的成员变量,a2是volatile的成员变量。使用两个线程对它们进行自增n次,观察最后的结果是否为2n。

public 

最后的结果是,a1和a2的表现差不多,有时候其中一个会刚好达到2n,但大多数情况,都是两个变量都无法成功的自增到2n(16000)

11135 
9527  

这也就验证了volatile关键字原子性操作的问题。

那么在实践中,如果出现需要同步的问题,依然还是要使用synchronized来解决,那么volatile应该在什么场景下使用呢?

volatile的应用场景

这个问题,我也是查了非常多的资料,花了几天的时间才搞清楚一点点。

正确的使用场景,基本符合一个原则:一写多读:有一个数据,只由一个线程更新,其他线程都来读取。

比如,有一个系统回调,告诉我们最新的设备的经纬度,而其他的线程需要去用这个经纬度做一些计算,那么这个经纬度就一直被第一个线程写,其他线程就只负责读取。此时,经纬度就很适合用volatile修饰,这样可以保证其他线程永远读取到的是最新的数值。

再比如,我们有一个WebSocket的封装类,里面包装了长连接的建立和发送数据的方法。那么可能会有好几个线程要使用这个封装类发送自己的请求。要求是当长连接断开了,要调用建立长连接的方法,为了维护长连接的状态标记,就需要这么一个状态flag,类似于boolean isConnected这种变量。此时,也很符合一写多读的场景,那么这个变量就可以用volatile进行修饰。

class 

总结

volatile的作用是很微妙的,它并不能替代synchronized,因此它无法提供同步的能力,它只能提供改变可见性的能力。

由于总是读写与主存,它的读写性能要低于普通的变量。

正确使用的模式总结下来就是一个线程写,多个线程读。

但也不要过于迷信它的功效,大部分情况下,都完全不需要使用这个关键字的。

参考资料

下面是我查阅的资料,供大家在补充阅读以下哦

  • on-properly-using-volatile-and-synchronized
  • volatile-vs-synchronized
  • volatile
  • IBM: Managing volatility
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: volatile关键字可以保证变量在多线程环境下的可见和有序。当一个变量被声明为volatile时,每次访问该变量时都会从内存中读取最新的值,而不是使用缓存中的旧值。同时,volatile还可以防止指令重排,保证指令执行的顺序与代码中的顺序一致。在多线程环境下,使用volatile可以避免出现线程安全问题。 ### 回答2: 在Java中,volatile是一种关键字,用于确保变量在多个线程之间具有可见。它的作用可以总结为以下几点: 1. 可见volatile变量的值会被立即更新到主内存中,而不缓存在本地线程缓存中。这意味着,当一个线程修改了一个volatile变量的值,其他线程将立即感知到该变化。 2. 有序volatile变量的读写操作具有有序,即其操作不会被重排序。即使在多个线程中发生了多次操作,重排序的问题也会得到避免。这对于保证线程安全非常重要。 3. 禁止指令重排:volatile关键字禁止编译器和处理器对其修饰的变量进行指令重排。这可以防止一些潜在的问题,例如由于指令重排导致的单例模式失效等。 需要注意的是,volatile关键字只适用于一些特定的场景。它不能替代锁(synchronized关键字)的作用,因为它只能保证可见和有序,并不能保证原子。对于需要满足原子的操作,仍然需要使用锁机制。 另外,由于volatile变量的特殊,它的使用会增加一定的开销。因此,在使用volatile关键字时,需要仔细考虑其在能上可能带来的影响,并且只在必要的情况下使用它。 总之,volatile关键字主要用于确保多线程环境下的变量可见和有序。它是一种轻量级的同步机制,对于一些简单的场景可以起到很好的效果。 ### 回答3: 在Java中,volatile关键字用来修饰变量,表示该变量是易变的(可变的),并且每一次使用时都是从主内存中读取最新的值,而不是使用线程的本地缓存值。 volatile关键字主要具有以下作用: 1. 线程可见使用volatile关键字修饰的变量,当一个线程修改了该变量的值时,其他线程能够立即看到修改后的值,保证了变量对多线程的可见。 2. 禁止指令重排:在多线程环境下,JVM为了优化执行速度,可能会对指令进行重排序,而使用volatile关键字修饰的变量可以禁止指令重排,保证了有序。 3. 保证变量的一致:当多个线程同时修改一个变量时,没有同步机制的情况下可能会出现数据不一致的情况,而使用volatile关键字修饰的变量能够保证变量的一致,避免数据错误。 需要注意的是,volatile关键字只能保证变量的可见和有序,并不能解决变量自身的原子问题。如果需要保证变量的原子,可以使用synchronized关键字或者使用原子类。 总之,volatile关键字在多线程编程中起到了重要的作用,能够保证线程安全,提高多线程程序的正确能。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值