简介
volatile在多处理器开发中保证了“共享变量”的“可见性”。可见性指当一个线程修改一个共享变量时,另外一个线程能马上读到这个修改的值。
共享变量:共享变量都被存放在堆内存中,volatile只作用于共享变量。
实现原理
对volatile变量修饰的共享变量进行写操作的时候会多一行汇编代码
- 将当前处理器缓存行的数据会写回到系统内存
- 写回内存的操作会引起在其他CPU里缓存了该内存地址的数据无效
处理器为了提高处理速度,不直接和内存进行通讯,而是先将系统内存的数据读到内部缓存(L1,L2等)后再进行操作,但操作完之后不知道何时会写到内存,如果对声明了Volatile变量进行写操作,JVM就会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据写回到系统内存。在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存被修改,就会将当前处理器的缓存行设置成无效状态,当处理器要对这个数据进行修改操作的时候,会强制重新从系统内存里把数据读到处理器缓存里。
MESI协议
缓存行状态
- M(modified):表示缓存行仅存在于当前的缓存中,并且已经被更改。在该缓存行写回到主存之前,任何其他CPU都不能读取该缓存行的内容。
- E(exclusive):表示缓存行仅存在于当前的缓存中,并且未被修改。如果有其他CPU读取该行,则转移到Shared状态;如果修改该行,则转移到Modified状态。
- S(shared):表示有多个CPU共享该缓存行,且内容未被修改。
- I(invalid):表示缓存行已失效(未被使用)。
状态转换
除Invalid状态以外,所有状态的缓存行都可以进行读操作。
从状态定义就可以看出,只有Invalid状态的缓存行内容是无效的,必须从主存读取。
只有在状态为M或E时才能进行写操作,如果当前状态为S,则其它CPU中的同一行必须转移到状态I,这是通过发送RFO(request for ownership)广播实现的。
M或E状态下,缓存行都只存在于单个CPU中,S状态下多个CPU共享一行,因此必须将其他CPU的状态置为I,才能进行写操作。
除M以外的任意状态都可转移到I,M状态必须先写回到主存再丢弃。
只要内容未被修改,CPU可以再任意时刻丢弃一个缓存行,否则则必须先把修改的内容写回到主存。
处于M状态的缓存必须拦截其他CPU对同一行的读操作,并返回自身缓存中的数据。
这可以保证所有CPU读到的都是最新的内容,这种拦截操作称为snoop,数据不需要写回到主存,直接由M状态的缓存返回,状态由M转移到S。
处于S状态的缓存必须监听RFO广播,并转移到I状态。
当一个CPU修改S状态的缓存时,其余的缓存必须先转移到I状态,防止并发的写操作。
处于E状态的缓存必须拦截其他CPU的读操作,并转移到S状态。
当有其他CPU读E状态的缓存时,状态必须由独占转移到共享。
指令重排
为了提高代码执行性能,指令的执行顺序被编译器或者运行时环境调整顺序的现象
内存屏障
- 在volatile变量的写操作之后插入写屏障,插入写屏障之后,屏障之前的写操作对于其他CPU都是可见的。
- 在volatile变量的读操作之前插入读屏障,插入写屏障之后,屏障之后的读操作对于其他CPU都是最新的。