你张口就来volatile的理解是保证可见性,禁止指令重排,那扩展后你还能回答好吗

你张口就来volatile的理解是保证可见性,禁止指令重排,那扩展后你还能回答好吗,第1张

你张口就来volatile的理解是保证可见性,禁止指令重排,那扩展后你还能回答好吗

int a = 5; // 代码1
int b = 8; // 代码2
a = a + 4; // 代码3
int c = a + b; // 代码4

上面四行代码的执行顺序有可能是

JMM在是允许指令重排序的,在保证最后结果正确的情况下,处理器可以尽情的发挥,提高执行效率。

当多个线程执行代码的时候重排序的情况就更为突出了,各个CPU为了提高自己的效率,有可能会产生竞争情况,这样就有可能导致最终执行的正确性。

所以为了保证在多个线程下最终执行的正确性,将变量用volatile进行修饰,这样就会达到禁止指令重排序的效果(其实也可以通过加锁,还有一些其他已知规则来实现禁止指令重排序,但是我们这里只讨论volatile的实现方式)。

那么volatile是如何实现指令重排序的呢?

答案是:内存屏障

内存屏障是一组CPU指令,用于实现对内存 *** 作的顺序限制。 Java编译器,会在生成指令系列时,在适当的位置会插入内存屏障来禁止处理器对指令的重新排序。

volatile会在变量写 *** 作的前后加入两个内存屏障,来保证前面的写指令和后面的读指令是有序的。

volatile在变量的读 *** 作后面插入两个指令,禁止后面的读指令和写指令重排序。

有序性,不仅只有volatile能保证,其他的实现方式也能保证,但是如果每一种实现方式都要了解那对于开发人员来说就比较困难了。

所以从JDK5就出现了happen-before原则,也叫先行发生原则。 先行发生原则总结起来就是:如果一个 *** 作A的产生的影响能被另一个 *** 作B观察到,那么可以说,这个 *** 作A先行发生与 *** 作B。

这里所说的影响包括内存中的变量的修改,调用了方法,发送量消息等。

volatile中的先行发生原则是,对一个volat

《一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》

【docs.qq.com/doc/DSmxTbFJ1cmN1R2dB】 完整内容开源分享

ile变量的写 *** 作,先行发生于后面任何地方对这个变量的读 *** 作。

Volatile无法保证原子性

原子性,是指一个 *** 作过程要么都成功,要么都失败,是一个独立的完整的。

就像上面说的,如果多个线程对一个变量进行累加,那么肯定得不到想要的结果,因为累加就不是一个原子 *** 作。

要保证累加最终结果正确,要么对累加变量加锁,要么就用AotomicInteger这样的变量。


public class DoubleCheckLockSingleton implements Serializable{


private volatile static DoubleCheckLockSingleton doubleCheckLockSingleton = null;


private DoubleCheckLockSingleton(){}


public static DoubleCheckLockSingleton getInstance(){
//第一次检查实例是否已经存在,不存在则进入代码块
if(null == doubleCheckLockSingleton){
synchronized (DoubleCheckLockSingleton.class){
//第二次检查
if(null==doubleCheckLockSingleton){
doubleCheckLockSingleton = new DoubleCheckLockSingleton();
}
}
}

return doubleCheckLockSingleton;
}

}

为什么要进行双重检查呢? 当第一个线程走到第一次检查时发现对象为空,然后进入锁,第二次就检查时也为空,那么就去创建对象,但是这个时候又来了一个线程来到了第一次检查,发现为空,但是这个时候因为锁被占用,所以就只能阻塞等待,然后第一个线程创建对象成功了,由于对象是被volatile修饰的能够立即反馈到其他线程上,所以在第一个线程释放锁之后,第二个线程进入了锁,然后进行第二次检查时,发现对象已经被创建了,那么就不在创建对象了。从而保证的单例。

还有就是如果创建对象,步骤:

  1. 分配内存空间。

  2. 调用构造器,实例化。

  3. 返回内存地址给引用。

如果这三个指令顺序被重排了,那么当多线程来获取对象的时候就会造成对象虽然实例化了,但是没有分配内存空间,会有空指针的风险。 所以加上了volatile的对象,也保证了在第二次检查时不会被已经在创建过程中的对象有被检测为空的风险。

总结一下

volatile其实可以看作是轻量级的synchronized,虽然说volatile不能保证原子性,但是如果在多线程下的 *** 作本身就是原子性 *** 作(例如赋值 *** 作),那么使用volatile会由于synchronized。

volatile可以适用于,某个标识flag,一旦被修改了就需要被其他线程立即可见的情况。也可以修饰作为触发器的变量,一旦变量被任何一个线程修改了,就去触发执行某个 *** 作。

volatile的变量写 *** 作happen-before,后面任何对此volatile变量的读 *** 作。

写在最后

大家看完有什么不懂的可以在下方留言讨论.
谢谢你的观看。
觉得文章对你有帮助的话记得关注我点个赞支持一下!

作者:熬夜不加班
链接:https://juejin.cn/post/6917517239096705037

大家看完有什么不懂的可以在下方留言讨论.
谢谢你的观看。
觉得文章对你有帮助的话记得关注我点个赞支持一下!

作者:熬夜不加班
链接:https://juejin.cn/post/6917517239096705037

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5683354.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存