深度理解volatile关键字

Volatile

java中可以把字段声明为volatile的。比如:

public class AtomicInteger extends Number implements java.io.Serializable {
   // volatile变量
   private volatile int value;
}

但是volatile启什么作用呢?

作用

这里先说结论:

  1. volatile关键字是并发编程的时候来保证可见性
  2. 简单讲就是线程A修改完volatile字段,线程B立即来读的话,是能读到最新的值的。

这里面有个问题,就是问什么会有可见性的问题?

CPU多级缓存

CPU为了加快处理数据的速度,加入了缓存,加缓存的原则,依赖于局部性原理:

  1. 时间局部性
    某个数据项被访问后,可能很快会被再次访问的特性。
  2. 空间局部性
    某个数据项被访问后,与其地址相近的数据项可能很快被访问的特性。

我们可以利用局部性原理将计算机的存储器组织成为存储器层次结构

在这里插入图片描述

缓存的最小单位是缓存行(cache line),现在主流的CPU缓存行是 64bytes
Linux 系统可以通过cat /sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size命令查看缓存行大小。
Mac 系统可以通过sysctl hw.cachelinesize查看缓存行的大小。

缓存一致性问题 Cache coherence

cpu缓存模型,其实默认情况下是有问题的,特别是多线程并发运行的时候,导致各个cpu的本地缓存跟主内存没有同步,一个数据在各个地方可能都不一样,就会导致数据的不一致问题。
在这里插入图片描述

缓存一致性协议

这里限于文章篇幅,和突出文章主题,不展开讲了。
一句话就是,现在主流的CPU都是通过MESI协议来解决缓存一致性问题的。

Java怎么实现的?

java规范手册

我们先看一下java规范手册对volatile的描述。
在这里插入图片描述
注意上图中红框框里面的文字,也就是说,java规范里面有个happens-before的原则。规则中约定了哪些动作在实际运行过程中要发生于哪些动作之前。而volatile声明的字段就在happens-before的原则里面。

这是在语言层面做的规约,那么实际JVM中又是怎么实现的呢?

class文件&CPU指令

以hotspot为例,先看一下volatile声明的字段在class文件是什么样子的
添加volatile

int v;
  descriptor: I
  flags:

添加volatile

volatile int v;
  descriptor: I
  flags:ACC_VOLATILE

class文件中变量flags: ACC_VOLATILE

通过 jitwatch 工具查看,运行过程中CPU实际执行的指令是啥呢?
我们发现,加了volatile的变量,在保存的时候
多了一行汇编指令lock addl $0x0,(%rsp)

查询 IA32 手册:

在这里插入图片描述
在这里插入图片描述

简述一下原文的要点:
在执行相应指令时,使处理器的LOCK#信号被激活(将指令转换为原子指令)。在多处理器环境中,LOCK#信号确保在信号被激活期间,处理器独占任何共享内存

限制使用的命令ADD, ADC, AND, BTC, BTR, BTS, CMPXCHG, CMPXCH8B, CMPXCHG16B, DEC, INC, NEG, NOT, OR, SBB, SUB, XOR, XADD, XCHG.

总结下来就是lock前缀指令 + 缓存一致协议来实现的

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值