volatile关键词

volatile是Java虚拟机提供的轻量级的同步机制。volatile关键字有如下两个作用:

 1)保证了不同线程对这个共享变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。保证可见性。

 2)禁止进行指令重排序。保证有序性。

volatile保证可见性:

当修改了一个被volatile修饰的共享变量时,JMM会把该线程对应的工作内存中的共享变量值刷新到主内存中,此时拥有这个共享变量的值的线程的工作内存置为无效(反映到硬件层的话,就是CPU的L1或者L2缓存中对应的缓存行无效,然后cpu的缓存就直接从主内存中获取值),无效之后,这个值会再次从主内存中读取,此时读取到的就是最新修改过的值。

volatile保证有序性:

被volatile修饰的代码,在这个代码行之前的代码会在这句代码行之前被执行,在这代码行后面的代码,会在执行完这句代码行后才执行。

先来理解一下指令重排:

理解指令重排

计算机在执行程序时,为了提高性能,编译器和处理器的常常会对指令做重排,一般分以下3种

  • 编译器优化的重排
    编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
  • 指令并行的重排
    现代处理器采用了指令级并行技术来将多条指令重叠执行。如果不存在数据依赖性(即后一个执行的语句无需依赖前面执行的语句的结果),处理器可以改变语句对应的机器指令的执行顺序
  • 内存系统的重排
    由于处理器使用缓存和读写缓存冲区,这使得加载(load)和存储(store)操作看上去可能是在乱序执行,因为三级缓存的存在,导致内存与缓存的数据同步存在时间差。

其中编译器优化的重排属于编译期重排,指令并行的重排和内存系统的重排属于处理器重排。指令重排只会保证单线程中串行语义的执行的一致性,但并不会关心多线程间的语义一致性。在Java内存模型中,还提供了happens-before 原则来辅助保证程序执行的原子性、可见性以及有序性的问题,它是判断数据是否存在竞争、线程是否安全的依据。

volatile保证可见性和有序性的原理:

观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀的cpu指令”

lock前缀指令实际上相当于一个内存屏障(Memory Barrier,也成内存栅栏),它的作用有两个,一是保证特定操作的执行顺序,二是保证某些变量的内存可见性(利用该特性实现volatile的内存可见性)。由于编译器和处理器都能执行指令重排优化。如果在指令间插入一条Memory Barrier则会告诉编译器CPU,不管什么指令都不能和这条Memory Barrier指令重排序,也就是说通过插入内存屏障禁止在内存屏障前后的指令执行重排序优化。Memory Barrier的另外一个作用是强制刷出各种CPU的缓存数据,因此任何CPU上的线程都能读取到这些数据的最新版本(这是对应于cpu的层面的,在jmm层面,就是使工作内存的变量无效)。

volatil不能保证原子性的原因

原子性:在整个操作过程中不会被线程调度器中断的操作,都可认为是原子性。原子性是拒绝多线程交叉操作的,不论是多核还是单核,具有原子性的量,同一时刻只能有一个线程来对它进行操作。例如 a=1是原子性操作,但是a++和a +=1就不是原子性操作。

volatile 关键字本质上是一种内存屏障。volatile 变量的写操作和读操作之间是可以被中断的,这意味着在读取或者修改 volatile 变量的过程中,其他线程可能会对这个变量进行修改。因此,使用 volatile 变量并不能保证对变量的操作是原子性的。

例子:

对于i=1这个赋值操作,由于其本身是原子操作,因此在多线程程序中不会出现不一致问题,但是对于i++这种复合操作,即使使用volatile关键字修饰也不能保证操作的原子性,可能会引发数据不一致问题。

 private volatile int i = 0;
 i++;

如果启了500条线程并发地去执行i++这个操作  最后的结果i是小于500的

 i++操作可以被拆分为三步:

      1,线程读取i的值

      2、i进行自增计算

      3、刷新回i的值

解释:

假设某一时刻i=5,此时有两个线程同时从主存中读取了i的值,那么此时两个线程保存的i的值都是5, 此时A线程对i进行了自增计算,然后B也对i进行自增计算,此时两条线程最后刷新回主存的i的值都是6(本来两条线程计算完应当是7)所以说volatile保证不了原子性。

1、线程读取i

2、temp = i + 1

3、i = temp

当 i=5 的时候A,B两个线程同时读入了 i 的值, 然后A线程执行了 temp = i + 1的操作, 要注意,此时的 i 的值还没有变化,然后B线程也执行了 temp = i + 1的操作,注意,此时A,B两个线程保存的 i 的值都是5,temp 的值都是6, 然后A线程执行了 i = temp (6)的操作,此时i的值会立即刷新到主存并通知其他线程保存的 i 值失效, 此时B线程需要重新读取 i 的值那么此时B线程保存的 i 就是6,同时B线程保存的 temp 还仍然是6, 然后B线程执行 i=temp (6),所以导致了计算结果比预期少了1。

volatile和synchronized的区别

(1)、volatile只能作用于变量,使用范围较小。synchronized可以用在变量、方法、类、同步代码块等,使用范围比较广。   

(2)、volatile只能保证可见性和有序性,不能保证原子性。而可见性、有序性、原子性synchronized都可以包证。   

(3)、volatile不会造成线程阻塞。synchronized可能会造成线程阻塞。   

(4)、在性能方面synchronized关键字是防止多个线程同时执行一段代码,就会影响程序执行效率,而volatile关键字在某些情况下性能要优于synchronized。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值