Volatile 关键字
最近,在一篇文章中了解到了 volatile 关键字,查阅了一些相关资料进行了学习,并将学习笔记记录如下,希望能给小伙伴们带来一些帮助。如果文章内容存在一些错误,也请小伙伴们指正,感谢。
volatile 的作用
大家都应该知道 volatile 的主要作用有两点:
1、 保证变量的内存可见性 。
2、禁止指令重排序。
内存可见性
那么什么是内存可见性呢?
内存可见性是指当一个线程修改了某个变量的值,其它线程总是能知道这个变量变化。也就是说,如果线程 A 修改了共享变量 i 的值,那么线程 B 在使用 i 的值时,能立即读到 i的最新值。
可见性问题的解决方案
我们如何保证多线程下共享变量的可见性呢?也就是当一个线程修改了某个值后,对其他线程是可见的。
这里有两种方案:加锁 和 使用 volatile 关键字。
禁止指令重排序
什么是重排序?
计算机在执行程序时,为了提高性能,编译器和处理器常常会对指令重排,一般分为以下三种
1、编译器优化重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
2、指令级并行重排序。现代处理器采用了指令级并行技术来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
3、内存系统重排序。由于处理器使用缓存和读 / 写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。
为了更好地理解重排序,请看下面的部分示例代码:
public void mySort() {
int x = 11;
int y = 12;
x = x + 5;
y = x * x;
}
按照正常单线程环境,执行顺序是 1 2 3 4
但是在多线程环境下,可能出现以下的顺序:
2 1 3 4
1 3 2 4
上述的过程就可以当做是指令的重排,即内部执行顺序,和我们的代码顺序不一样但是指令重排也是有限制的,即不会出现下面的顺序
4 3 2 1
因为处理器在进行重排时候,必须考虑到指令之间的数据依赖性.因为步骤 4:需要依赖于 y的申明,以及x的申明,故因为存在数据依赖,无法首先执行.
Volatile针对指令重排做了啥
Volatile实现禁止指令重排优化,从而避免了多线程环境下程序出现乱序执行的现象.
首先了解一个概念,内存屏障(Memory Barrier)又称内存栅栏,是一个CPU指令,它的作用有两个:
1、保证特定操作的顺序
2、保证某些变量的内存可见性(利用该特性实现volatile的内存可见性)
由于编译器和处理器都能执行指令重排的优化,如果在指令间插入一条Memory Barrier则会告诉编译器和CPU,不管什么指令都不能和这条Memory Barrier指令重排序,也就是说 通过插入内存屏障禁止在内存屏障前后的指令执行重排序优化。 内存屏障另外一个作用是刷新出各种CPU的缓存数,因此任何CPU上的线程都能读取到这些数据的最新版本。
也就是过在Volatile的写 和 读的时候,加入屏障,防止出现指令重排的