Volatile
Volatile的作用
Volatile是JVM提供的轻量级的同步机制,主要有以下三个功能
- 保证可见性
- 不保证原子性
- 禁止指令重排
Volatile如何实现可见性
当某个线程修改了某个值以后,写入主内存。此时其他线程会被通知重新去主内存中读取值。Java内存模型中规定:所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问,但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝到自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写会主内存,不能直接操作主内存中的变量。各个线程中的工作内存中存储着主内存中的变量副本拷贝,因此不同的线程间无法访问对方的工作内存,线程间的通信(传值)必须通过主内存来完成
当使用Volatile关键字修饰的共享变量转换成汇编时,会添加一个以lock为前缀的指令,当CPU执行该指令时,需要做两件事:
- 将当前内核高速缓存行的数据立刻回写到内存
- 使在其他内核里缓存了该内存地址的数据无效,这里使用CPU的MESI协议实现
MESI协议简单说就是:
当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,那么他会发出信号通知其他CPU将该变量的缓存行设置为无效状态。当其他CPU使用这个变量时,首先会去嗅探是否有对该变量更改的信号,当发现这个变量的缓存行已经无效时,会从新从内存中读取这个变量
为什么Volatile不保证原子性
当Volatile转化成汇编时,并没有添加锁的操作。因此对于复合操作如num++不具有原子性。但是对于单个变量的读写仍然具有原子性
Volatile指令重排
编译器和处理器常常会对指令重排。一般分为以下三种:
1 | 源代码 -> 编译器优化的重排 -> 指令并行的重排 -> 内存系统的重排 -> 最终执行指令 |
由于编译器和处理器都能执行指令重排的优化。Volatile指令在生成字节码的同时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。
- 在每个volatile写操作的前面插入一个StoreStore屏障:禁止上面的普通写和下面的volatile写重排序
- 在每个volatile写操作的后面插入一个StoreLoad屏障:防止上面的volatile写与下面可能有的volatile重排序
- 在每个volatile读操作的后面插入一个LoadLoad屏障:禁止下面所有的普通读操作和上面的volatile读重排序
- 在每个volatile读操作的后面插入一个LoadStore屏障:禁止下面所有的普通写和上面的volatile读重排序
在指令间插入一条Memory Barrier则会告诉编译器和CPU,不管什么指令都不能和这条Memory Barrier指令重排序,也就是说通过插入内存屏障禁止在内存屏障前后的指令执行重排序优化。 内存屏障另外一个作用是刷新出各种CPU的缓存数,因此任何CPU上的线程都能读取到这些数据的最新版本。