阿里面试题: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
硬核面试题推荐
- 惊呆:Mysql 普通索引、唯一索引,底层区别竟然 这么大 !!(进大厂必备,赶紧私藏)
- 2023高薪必备:刨根问底 100 个 Nginx连环炮, 让 大厂 面试官 震惊到 怀疑人生 !!
- 架构必备:10WQPS超高并发架构的10大思想
- 如何写简历:2023春招,让简历 人见人爱 的8大绝招 | 附100模板
- 聊聊:什么是脑裂?照此文作答,面试官献上膝盖,秒发offer
- 场景题:假设10W人突访,系统能不crash 吗?
- 每天100w次登陆请求, 8G 内存该如何设置JVM参数?来看看年薪100W的架构师,是怎么配置的
- 核心面试题:为什么新生代要两个Survivor区? 一个不行吗?
- 核心面试题目:什么是索引下推?什么是 MRR 优化?怎么才能更好的为表创建索引?
- 核心面试题目:什么是 回表查询、索引覆盖、最左匹配原则?说说聚集索引、非聚集索引的区别?
- 面试重点难题:Mysql如何实现RR级隔离时,不会幻读?
- 核心面试难题:Java对象为什么 不一定在堆上分配?
- 核心面试题:MVCC、间隙锁、Undo Log链、表级锁、行级锁、页级锁、共享锁、排它锁、记录锁等等
硬核文章推荐
- 100w人在线的 弹幕 系统,是怎么架构的?
- 峰值21WQps、亿级DAU,小游戏《羊了个羊》是怎么架构的?
- 2个大厂 100亿级 超大流量 红包 架构方案
- 一文搞懂:Java高手必备之 Mpsc 无锁队列 (史上最全)
- 一文秒懂:多级时间轮,最顶尖的Java调度算法
- 一文搞懂:缓存之王 Caffeine 架构、源码、原理(5W长文)
- 一文穿透:队列之王 Disruptor 原理、架构、源码
- 细思极恐:Java官方JVM 为啥要叫做 HotSpot JVM?背后的水,不知道有多深!!!
硬核电子书
本文收录于 《尼恩Java面试宝典》V30版
长按二维码,点击识别图中二维码即可查看老架构师尼恩个人微信,
发暗号 领电子书 给尼恩,获取下面的 价值10W 以上的宝贵资源:
👍《尼恩Java面试宝典》(极致经典,不断升级)全网下载超过300万次
👍尼恩Java高并发三部曲:全网下载超过200万次
- 👍《Java高并发核心编程-卷1(加强版)》,不断升级
- 👍《Java高并发核心编程-卷2(加强版)》,不断升级
- 👍《Java高并发核心编程-卷3(加强版)》,不断升级
👍《顶级3高架构行业案例 + 尼恩架构笔记 》N 篇+,不断添加
👍100份简历模板
面试题133
面试题 · 目录
上一篇惊呆:Mysql 普通索引、唯一索引,底层区别竟然 这么大 !!(进大厂必备,赶紧私藏)下一篇美团一面:聊聊MySQL的七种日志
阅读 457
技术自由圈
分享收藏在看9
发消息
复制搜一搜分享收藏划线
人划线