【面试官】volatile有什么缺点吗?

  • 面试官:重排序知道吧?
  • 面试官:那重排序不会有什么问题吗?
  • 面试官:有什么办法可以解决?
  • 面试官:那volatile可见性的原理是什么?
  • 面试官:volatile有什么缺点吗?

大家好,我是南哥。

一个Java学习与进阶的领路人,跟着南哥我们一起Java成长。

文章目录

  1. 指令重排序
    1. 重排序是什么
    2. 重排序的问题
  2. volatile
    1. volatile特性
    2. 可见性原理
    3. volatile局限性

1. 指令重排序

1.1 重排序是什么

面试官:重排序知道吧?

指令重排序字面上听起来很高级,但只要理解了并不难掌握。我们先来看看指令重排序究竟有什么作用。

指令重排序的主要作用是可以优化编译器和处理器的执行效率,提高程序性能。例如多条执行顺序不同的指令,可以重排序让轻耗时的指令先执行,从而让出CPU流水线资源供其他指令使用。

但如果指令之间存在着数据依赖关系,则编译器和处理器不会对相关操作进行指令重排序,避免程序执行结果改变。这个规则也称为as-if-serial语义。例如以下代码。

String book = "JavaGetOffer"; // A
String avator = "思考的陈"; // B
String msg = book + abator; // C

对于A、B,它们之间并没有依赖关系,谁先执行对程序的结果没有任何影响。但C却依赖于A、B,不能出现类似C -> A -> B或C -> B -> A或A -> C -> B或B -> C -> A之类的指令重排,否则程序执行结果将改变。

1.2 重排序的问题

面试官:那重排序不会有什么问题吗?

在单线程环境下,有as-if-serial语义的保护,我们无需担心程序执行结果被改变。但在多线程环境下,指令重排序会出现数据不一致的问题。举个多线程的例子方便大家理解。

       int number = 0;
       boolean flag = false;
       public void method1() {
           number = 6;                // A
           flag = true;               // B
       }
       public void method2() {
           if (flag) {               // C
               int i = number * 6;   // D
           }
       }

假如现在有两个线程,线程1执行method1、线程2执行method2。因为method1其中的A、B之间没有数据依赖关系,可能出现B -> A的指令重排序,大家注意这个指令重排序会影响到线程2执行的结果。

当B指令执行后A指令还没有执行number = 6,此时如果线程2执行method2同时给i赋值为0 * 6。很明显程序运行结果和我们预期的并不一致。

2. volatile

2.1 volatile特性

面试官:有什么办法可以解决?

关于上文的重排序问题,可以使用volatile关键字来解决。volatile一共有以下特性:

  1. 可见性。volatile修饰的变量每次被修改后的值,对于任何线程都是可见的,即任何线程会读取到最后写入的变量值。
  2. 原子性。volatile变量的读写具有原子性。
  3. 禁止代码重排序。对于volatile变量操作的相关代码不允许重排序。
       int number = 0;
       volatile boolean flag = false;
       public void method1() {
           number = 6;                // A
           flag = true;               // B
       }
       public void method2() {
           if (flag) {               // C
               int i = number * 6;   // D
           }
       }

由于volatile具有禁止代码重排序的特性,所以不会出现上文的B -> A的指令重排序。另外volatile具有可见性,falg的修改对线程2来说是可见的,线程会立刻感知到flag = ture从而执行对i的赋值。以上问题可以通过volatile解决,和使用synchronized加锁是一样的效果。

另外大家注意一点,volatile的原子性指的是对volatile的读、写操作的原子性,但类似于volatile++这种复合操作是没有原子性的。

2.2 可见性原理

面试官:那volatile可见性的原理是什么?

内存一共分为两种,线程的本地内存和线程外的主内存。对于一个volatile修饰的变量,任何线程对该变量的修改都会同步到主内存。而当读一个volatile修饰的变量时,JMM(Java Memory Model)会把该线程对应的本地内存置为无效,从而线程读取变量时读取的是主内存。

线程每次读操作都是读取主内存中最新的数据,所以volatile能够实现可见性的特性。

2.3 volatile局限性

面试官:volatile有什么缺点吗?

企业生产上还是比较少用到volatile的,对于加锁操作会使用的更多些。

  1. synchronized加锁操作虽然开销比volatile大,但却适合复杂的业务场景。而volatile只适用于状态独立的场景,例如上文对flag变量的读写。
  2. volatile编写的代码是比较难以理解的,不清楚整个流程和原理很难维护代码。
  3. 类似于volatile++这种复合操作,volatile不能确保原子性。

最后,评论区说它好孤独的!!它需要你们的吐槽。关于Java技术、牛友的迷茫、简历等问题来评论区和南哥、牛油们一起吐槽分析分析。

创作不易,不妨点赞、收藏、关注支持一下,各位的支持就是我创作的最大动力❤️❤️

#Java##Java面试##秋招##面试##应届生#
Java面试突击大厂 文章被收录于专栏

👉以面试官面试的形式,涵盖了你怒怼大厂面试官、拿下大厂面试所需掌握的核心知识、面试重点 👉相信一定对你顺利通关面试、拿到理想Offer有所帮助 👉花费大量精力去制作本专栏,创作不易,各位的支持就是我创作的最大动力

全部评论

相关推荐

07-17 14:55
广州大学 Java
# Moka后端开发实习二面 2024.7.12忘记记录了,只能根据记忆回忆了1. 自我介绍2. 没让我介绍项目(这里已经感觉不妙了)3. 数组和链表的区别,各自的应用场景4. ArrayList扩容,使用的时候要注意什么?扩容大小等一系列相关问题5. HashMap的底层实现,怎么分辨插入和更新?什么情况转红黑树?什么情况退化?1.7是头插法,1.8是尾插,为什么这么设计?什么情况会发生循环链表问题?负载因子是什么?6. 讲讲ConcurrentHashMap7. 多线程创建的方式?彼此的区别,特点?8. 了解哪些锁?Synchronized,ReentranLock,还有呢?(当时想不起来其他的了)9. java中有哪些同步的方式?(锁,Thread.join(),Thread.sleep(),Object.Wait()和notifiy()、notifyAll())10. 说一下volatile,起什么作用,机制是什么?11. 聚簇索引和非聚簇索引12. 说说最左匹配原则,举了(a,b,c)例子,那查询a,b和a,c生效吗?13. 索引下推是什么?14. 讲讲MVCC,如何判断该选哪个版本的?15. (上一问顺带讲了隔离级别)读已提交和可重复读的锁实现有什么区别?(小林coding看过,但忘记了。间隙锁那些)16. 讲讲垃圾收集算法17. 哪些对象可以作为GCRoots对象?18. 缓存了解吗?缓存更新策略有哪些?(想不起来了)19. 反问,然后建议我语速快一点总共面了一个小时
查看17道真题和解析
点赞 评论 收藏
分享
13 39 评论
分享
牛客网
牛客企业服务