2024全网最全volatile专题(25秋招走起~)

阿里面试题:volatile 夺命 连环炮 !答垮了就挂,非常重要!

技术自由圈 2023-01-20 10:42 湖南

技术自由圈

疯狂创客圈(技术自由架构圈):一个 技术狂人、技术大神、高性能 发烧友 圈子。圈内一大波顶级高手、架构师、发烧友已经实现技术自由;另外一大波卷王,正在狠狠卷,奔向技术自由

150篇原创内容

公众号

本文是一道非常核心的大厂面试题,涉及 volatile 的夺命连环炮。

volatile 在高并发编程中非常常用,而底层原理又很复杂,如果答题垮掉,面试基本也就挂掉了。

所以,此文给大家奉上一个非常重要的 大厂 连环炮 面试题,大家收藏起来,慢慢吸收和消化吧。

此题的内容,收录于《尼恩Java面试宝典》 V30版

聊聊:volatile的作用?

volatile的作用就是,核心是两个:

  • 「保证变量对所有线程可见性」
  • 「禁止指令重排」

但是

  • 「不保证原子性」

所以当面试官问你「volatile的作用或者特性」,都可以这么回答:

  • 保证变量对所有线程可见性;
  • 禁止指令重排序
  • 不保证原子性

关于:重排序,可见性的底层原理,请参见:

《Java高并发核心编程(卷2):多线程、锁、JMM、JUC、高并发设计模式》

聊聊:volatile的典型场景

通常来说,使用volatile必须具备以下2个条件:

  • 1)对变量的写操作不依赖于当前值
  • 2)该变量没有包含在具有其他变量的不变式中

实际上,volatile场景一般就是

  • 状态标志
  • DCL单例模式
  • CAS 轻量级乐观锁场景

场景1:状态标志场景

深入理解Java虚拟机,书中的例子:

Map configOptions;
char[] configText;
// 此变量必须定义为 volatile
volatile boolean initialized = false;

// 假设以下代码在线程 A 中运行
// 模拟读取配置信息, 当读取完成后将 initialized 设置为 true 以告知其他线程配置可用
configOptions = new HashMap();
configText = readConfigFile(fileName);
processConfigOptions(configText, configOptions);
initialized = true;
      
// 假设以下代码在线程 B 中运行
// 等待 initialized 为 true, 代表线程 A 已经把配置信息初始化完成
while(!initialized) {
   sleep();
}
// 使用线程 A 中初始化好的配置信息
doSomethingWithConfig();

场景2:DCL单例模式

class Singleton{
    private volatile static Singleton instance = null;
     
    private Singleton() {   
    }
     
    public static Singleton getInstance() {
        if(instance==null) {
            synchronized (Singleton.class) {
                if(instance==null)
                    instance = new Singleton();
            }
        }
        return instance;
    }
}

场景3:CAS 轻量级乐观锁场景

JUC AQS源码中,其核心的成员 state (同步状态)、head (头指针)、tail (尾部指针), 都是通过CAS进行乐观锁修改的,都需要进行 volatile保证可见性。

同样,在高性能组件 caffeine 、 disruptor 源码中, 都是CAS + volatile 配合使用的

caffeine 、 disruptor 源码, 请参见 第24章、 第25章 视频。

聊聊:volatile的内存语义

  • 当写一个 volatile 变量时,JMM 会把该线程对应的本地内存中的共享变量值刷新到主内存。
  • 当读一个 volatile 变量时,JMM 会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。

聊聊:什么是内存可见性,什么是指令重排序?

  • 可见性就是指当一个线程修改了共享变量的值时,其他线程能够立即得知这个修改。
  • 指令重排是指JVM在编译Java代码的时候,或者CPU在执行JVM字节码的时候,对现有的指令顺序进行重新排序。

聊聊:volatile重排序规则

volatile禁止重排序场景:

  • 第二个操作是volatile写,不管第一个操作是什么都不会重排序。
  • 第一个操作是volatile读,不管第二个操作是什么都不会重排序。
  • 第一个操作时volatile写,第二个操作时volatile读,也不会发生重排序。

聊聊:并发编程的3大特性

  • 原子性
  • 可见性
  • 有序性

聊聊:volatile是如何解决java并发中可见性的问题

底层是通过内存屏障实现的哦,

volatile能保证修饰的变量后,可以立即同步回主内存,每次使用前立即先从主内存刷新最新的值。

聊聊:volatile底层的实现机制

volatile如何保证可见性和禁止指令重排,

底层是通过内存屏障实现的哦,

聊聊:volatile如何防止指令重排

底层是通过内存屏障实现防止指令重排的哦,

跟面试官讲下Java内存的保守策略:

  • 1、在每个volatile写操作的前面插入一个StoreStore屏障。
  • 2、在每个volatile写操作的后面插入一个StoreLoad屏障。
  • 3、在每个volatile读操作的后面插入一个LoadLoad屏障。
  • 4、在每个volatile读操作的后面插入一个LoadStore屏障。

聊聊:你对内存屏障理解?

分为两个层面:

  • JVM层面的内存屏障
  • 硬件层面内存屏障

JVM层面的内存屏障

  • LoadLoad屏障:(指令Load1;LoadLoad;Load2),在Load2及后续 读取操作要读取的数据访问前,保障Load1要读取的数据被读取完毕。
  • LoadStore屏障:(指令Load1;LoadStore;Store2),在Store2及后续写入操作被刷出前,保障Load1要读取的数据被读取完毕。
  • StoreStore屏障:(指令Store1;StoreStore;Store2),在Store2及后续写入操作执行前,保障Store1的写入操作对其他处理器可见;
  • StoreLoad屏障:(指令Store1;StoreLoad;Load2),在Load2及后续所有读取操作执行前保障Store1的写入对所有处理器可见。它的开销时四种屏障中最大的。在大多数处理器的实现中,这个屏障是个万能屏障;间距其他三种内存屏障的功能。

硬件层面内存屏障

硬件层提供了一系列的内存屏障memory barrier/memory fence来提供一致性的能力,拿X86平台来说,有以下几种内存屏障:

  • lfence,是一种Load Barrier读屏障,在读指令前插入读屏障,可以让高速缓存中的数据失效,重新从主内存加载数据。
  • sfence,是一种Store Barrier写屏障,在写指令之后插入写屏障,能让写入缓存的最新数据写回到主内存。
  • mfence,是一种全能型的屏障,具备lfence和sfence的能力
  • Lock前缀,Lock不是一种内存屏障,但是它能完成类似内存屏障的功能。Lock会对CPU总线或高速缓存加锁,可以理解为CPU指令级的一种锁。它先对高速缓存加锁,然后执行后面的指令,最后释放锁后会把高速缓存中的数据刷新回主内存。在Lock锁总线的时候,其他CPU的读写请求斗会被阻塞,直到锁释放。

不同硬件实现内存屏障的方式不同,Java内存模型屏蔽了这种底层硬件平台的差异,由JVM来为不同的平台生成相应的机器码。

聊聊:内存屏障的作用?

两个作用:

  • 阻止屏障两边的指令重排序
  • 刷新处理器缓存

聊聊:volatile可以解决原子性嘛?为什么?

不可以,可以直接举i++那个例子,原子性需要synchronzied或者lock保证

public class Test {
    public volatile int race = 0;
     
    public void increase() {
        race++;
    }
     
    public static void main(String[] args) {
        final Test test = new Test();
        for(int i=0;i<10;i++){
            new Thread(){
                public void run() {
                    for(int j=0;j<100;j++)
                        test.increase();
                };
            }.start();
        }
        
        //等待所有累加线程结束
        while(Thread.activeCount()>1)  
            Thread.yield();
        System.out.println(test.race);
    }
}

具体展开介绍内容非常多,具体请细度 《java高并发核心编程 卷2 加强版》的相关章节, pdf 参见文末

聊聊:volatile和synchronized的区别?

  • volatile修饰的是变量,synchronized一般修饰代码块或者方法
  • volatile保证可见性、禁止指令重排,但是不保证原子性;synchronized可以保证原子性
  • volatile不会造成线程阻塞,synchronized可能会造成线程的阻塞,所以后面才有锁优化那么多故事~

End

硬核面试题推荐

硬核文章推荐

硬核电子书

本文收录于 《尼恩Java面试宝典》V30版

长按二维码,点击识别图中二维码即可查看老架构师尼恩个人微信,

发暗号 领电子书 给尼恩,获取下面的 价值10W 以上的宝贵资源:

👍尼恩Java面试宝典》(极致经典,不断升级)全网下载超过300万次

👍尼恩Java高并发三部曲:全网下载超过200万次

👍《顶级3高架构行业案例 + 尼恩架构笔记 》N 篇+,不断添加

👍100份简历模板

面试题133

面试题 · 目录

上一篇惊呆:Mysql 普通索引、唯一索引,底层区别竟然 这么大 !!(进大厂必备,赶紧私藏)下一篇美团一面:聊聊MySQL的七种日志

阅读 457

技术自由圈

分享收藏在看9

发消息

复制搜一搜分享收藏划线

人划线

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值