volatile关键字
可以保证可见性,一点程度上保证了有序性,不能保证原子性。
总结:
- 它会强制将对缓存的修改操作立即写入主存
- 如果是写操作,它会导致其他CPU中对应的缓存行无效
- 它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成
volatile int i = 10;
int get(){
i++;// 不是原子操作 读取-->修改-->写回
return i;
}
线程工作的时候,会先将 i = 10从主存读到自己的工作内存,然后CPU将 i = 10 读到寄存器中进行运算,运算完成将结果先写入工作内存中,最后在同步到主存中。
volatile是如果保障可见性的?
他会强制要求写入工作内存的值同时写入主存中保证这一个过程不可打断,同时会发信号使其他线程的工作内存的对应缓存行失效,将重新从主存中读取。
为什么说一定程度保证了有序性?
可以在一定程度上禁止指令重排,根据JVM规范:在volatile变量之前的语句必须在它前面,在他后面的语句必须在它后面。
在线程A等待初始完成后进行操作
while( !contextReady ){
sleep(200);
}
doAfterContextReady (context);
在线程B用来初始化资源
context = loadContext();// 语句1
contextReady = true; // 语句2
线程A等待初始化工作完成后然后执行doAfterContextReady (context),若发生指令重排语句2先执行,接着线程切到线程B,线程B误以为初始化工作完成,然后执行doAfterContextReady (context)会使用未初始化的context对象,会引发异常。
单线程情况下:JVM会保证数据传递的正确性,下面代码没有任何问题,其中 2 - 5 行可以不要
context = loadContext();// 语句1
contextReady = true; // 语句2
while( !contextReady ){
sleep(200);
}
doAfterContextReady (context);
为什么说volatile不能保证原子性?
当线程A将 i = 10从主存读到工作内存,然后再将 i = 10从工作内存读取到CPU寄存器A中,然后线程A中的值自增1(是可以的volatile只能保证写到工作内存的值立即写入主存中,此时还没开始写,自增的结果还保存在CPU寄存器A中,也就是说工作内存和主存中的 i 还是等于 10)。,此时发生了线程切换,线程B也进行了同样的操作,从主存中读取 i = 10 ---> 工作内存 i = 10 ----> CPU寄存器 i = 10,CPU运算 10 + 1,CPU将运算的结果写回工作内存 i = 11 --->工作内存同步到主存 i = 11。此时将线程A的对应缓存行 i = 10失效,重新从主内存读取,变成 i = 11。这个时候线程A获取CPU时间片接着执行,将寄存器A中的 i = 11赋值给工作内存,执行结果也是 i = 11。经过两次运算正确的结果应该是 i = 12,这样就出现了线程安全问题。