volatile关键字作用(变量可见性、禁止指令重排序)
前置知识:内存读取的速度跟不上CPU的速度(会降低CPU执行指令的速度),所以有了CPU高速缓存。 也就是说,CPU在从主存读取数据的时候,会从复制一份数据到缓存中,运算结束再把数据刷新到主存中。 MSI协议(缓存一致性):主存有一个共享变量,多个CPU缓存复制一份副本,如果主存中共享变量修改,所以CPU缓存中副本都失效。
1、变量可见性(变量修改->重新到主存读数据)
当一个变量被修饰为volatile的时候,一旦这个变量被写修改了,其他缓存中这个变量的副本就会全部失效,因此得重新到主存中读最新的变量。 这就保证了当一个线程修改了某个值,其他线程就可以立即获取。
2、禁止指令重排序
举个例子,创建对象实例需要三个过程
- 在堆中开辟内存
- 调用构造器进行初始化
- 将对象引用复制给变量
但是由于指令重排并不保证2一定在3前面执行,但1一定是最先执行的的(2,3对1有依赖关系) 如果此时3先执行,然后访问成员,由于还没执行2的初始化,会导致异常。 表示加入内存屏障,告诉CPU和编译器,这个屏障前后的指令不能进行重排序。
volatile不保证原子性
有个变量 int cnt=0 ;
- 线程A和线程B同时读到缓存中都是
cnt=0 - 线程A先执行
cnt=cnt+1 ,得到cnt=1 存放到寄存器A中,然后线程A就阻塞了。由于寄存器A中的值还未刷新到主存中,所以主存中的数据还是cnt=0 ,线程B中cnt=0 缓存还不会失效。 - 此时线程B马上执行
cnt=cnt+1 ,并马上刷回到主存中,此时触发MSI(缓存一致性原则),导致线程A中缓存值失效。 - 线程A中值cnt失效,重新到主存中获取
cnt=1 ,然后再把寄存器(MSI协议并不会映像到寄存器)中的cnt=1 刷新到主存中,这就导致了线程安全问题。
|