Volatile
java中可以把字段声明为volatile
的。比如:
public class AtomicInteger extends Number implements java.io.Serializable {
// volatile变量
private volatile int value;
}
但是volatile
启什么作用呢?
作用
这里先说结论:
volatile
关键字是并发编程的时候来保证可见性
的- 简单讲就是线程A修改完
volatile
字段,线程B立即来读的话,是能读到最新的值的。
这里面有个问题,就是问什么会有可见性的问题?
CPU多级缓存
CPU为了加快处理数据的速度,加入了缓存,加缓存的原则,依赖于局部性原理:
- 时间局部性
某个数据项被访问后,可能很快会被再次访问的特性。 - 空间局部性
某个数据项被访问后,与其地址相近的数据项可能很快被访问的特性。
我们可以利用局部性原理将计算机的存储器组织成为存储器层次结构。
缓存的最小单位是缓存行(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前缀指令 + 缓存一致协议来实现的