编译器优化带来的内存可见性问题
内存可见性问题:
- 当一个线程修改一个共享变量的值时,这个值会被保存在该线程的本地内存中,而不是直接写入主内存中。
- 如果其他线程需要读取该共享变量的值,它们可能会从自己的本地内存中读取旧值,而不是从主内存中读取最新的值,从而导致数据不一致的问题。
使用volatile保证内存可见性
内存可见性是指多个线程之间共享变量时,对变量的修改能够被其他线程及时地看到。
当一个变量被声明为volatile
时,每次读取该变量时,都会从主内存中读取最新的值,而不是从线程的本地内存中读取。同样,每次写入该变量时,都会立即将值刷新到主内存中,而不是仅在线程的本地内存中修改。
Volatile变量具备两种特性:
变量可见性就是保证该变量对所有线程可见,可见性是指当一个线程修改了某个变量的值,那么新值对与其他线程是可以立即获取的。
- 使用volatile关键字会强制将修改的值立即写入主存;
- 使用volatile关键字的话,当线程2进行修改时,会导致线程1的工作内存中缓存变量stop的缓存行无效(反映到硬件层的话,就是CPU的L1或者L2缓存中对应的缓存行无效);
- 由于线程1的工作内存中缓存变量stop的缓存行无效,所以线程1再次读取变量stop的值时会去主存读取。
- 在线程2修改stop值时,会使得线程1的工作内存中缓存变量stop的缓存行无效,然后线程1读取时,发现自己的缓存行无效,它会等待缓存行对应的主存地址被更新之后,然后去对应的主存读取最新的值。
- 那么线程1读取到的就是最新的正确的值。
禁止指令重排序,阻止编译器对代码的优化
volatile关键字禁止指令重排序有两层意思:
-
当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;
-
在进行指令优化时,不能把volatile变量前面的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。
为了保证volatile的内存语义,加入volatile关键字时,**编译器在生成字节码时,会在指令序列中插入内存屏障,会多出一个lock前缀指令。内存屏障是一组处理器指令,解决禁止指令重排序和内存可见性的问题。**编译器和CPU可以在保证输出结果一样的情况下对指令重排序,使性能得到优化。处理器在进行重排序时会考虑指令之间的数据依赖性。
内存屏障,有2个作用:
1、优先于这个内存屏障的指令必须先执行,后于这个内存屏障的指令必须后执行。
2、使得内存可见性。所以,如果你的字段是volatile,在读指令前插入读屏障,可以让高速缓存中的数据失效,重新从主内存加载数据。在写指令之后插入写屏障,能让写入缓存的最新数据写回到主内存。