首页
题库
公司真题
专项练习
面试题库
在线编程
面试
面试经验
AI 模拟面试
简历
求职
学习
基础学习课
实战项目课
求职辅导课
专栏&文章
竞赛
我要招人
发布职位
发布职位、邀约牛人
更多企业解决方案
AI面试、笔试、校招、雇品
HR免费试用AI面试
最新面试提效必备
登录
/
注册
牛客807710847号
南昌大学 Java
发布于江西
关注
已关注
取消关注
@Java三段:
八股整理:说说volatile底层实现原理?
在 Java 并发编程中,有 3 个最常用的关键字:synchronized、ReentrantLock 和 volatile。虽然 volatile 并不像其他两个关键字一样,能保证线程安全,但 volatile 也是并发编程中最常见的关键字之一。例如,单例模式、CopyOnWriteArrayList 和 ConcurrentHashMap 中都离不开 volatile。那么,问题来了,我们知道 synchronized 底层是通过监视器 Monitor 实现的,ReentrantLock 底层是通过 AQS 的 CAS 实现的,那 volatile 的底层是如何实现的?1.volatile 作用在了解 volatile 的底层实现之前,我们需要先了解 volatile 的作用,因为 volatile 的底层实现和它的作用息息相关。volatile 作用有两个:保证内存可见性和有序性(禁止指令重排序)。1.1 内存可见性说到内存可见性问题就不得不提 Java 内存模型,Java 内存模型(Java Memory Model)简称为 JMM,主要是用来屏蔽不同硬件和操作系统的内存访问差异的,因为在不同的硬件和不同的操作系统下,内存的访问是有一定的差异得,这种差异会导致相同的代码在不同的硬件和不同的操作系统下有着不一样的行为,而 Java 内存模型就是解决这个差异,统一相同代码在不同硬件和不同操作系统下的差异的。Java 内存模型规定:所有的变量(实例变量和静态变量)都必须存储在主内存中,每个线程也会有自己的工作内存,线程的工作内存保存了该线程用到的变量和主内存的副本拷贝,线程对变量的操作都在工作内存中进行。线程不能直接读写主内存中的变量,如下图所示:然而,Java 内存模型会带来一个新的问题,那就是内存可见性问题,也就是当某个线程修改了主内存中共享变量的值之后,其他线程不能感知到此值被修改了,它会一直使用自己工作内存中的“旧值”,这样程序的执行结果就不符合我们的预期了,这就是内存可见性问题,我们用以下代码来演示一下这个问题:private static boolean flag = false;public static void main(String[] args) { Thread t1 = new Thread(new Runnable() { @Override public void run() { while (!flag) { } System.out.println("终止执行"); } }); t1.start(); Thread t2 = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("设置 flag=true"); flag = true; } }); t2.start();}以上代码我们预期的结果是,在线程 1 执行了 1s 之后,线程 2 将 flag 变量修改为 true,之后线程 1 终止执行,然而,因为线程 1 感知不到 flag 变量发生了修改,也就是内存可见性问题,所以会导致线程 1 会永远的执行下去,最终我们看到的结果是这样的:如何解决以上问题呢?只需要给变量 flag 加上 volatile 修饰即可,具体的实现代码如下:private volatile static boolean flag = false;public static void main(String[] args) { Thread t1 = new Thread(new Runnable() { @Override public void run() { while (!flag) { } System.out.println("终止执行"); } }); t1.start(); Thread t2 = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("设置 flag=true"); flag = true; } }); t2.start();}以上程序的执行结果如下图所示:1.2 有序性有序性也叫做禁止指令重排序。指令重排序是指编译器或 CPU 为了优化程序的执行性能,而对指令进行重新排序的一种手段。指令重排序的实现初衷是好的,但是在多线程执行中,如果执行了指令重排序可能会导致程序执行出错。指令重排序最典型的一个问题就发生在单例模式中,比如以下问题代码:public class Singleton { private Singleton() {} private static Singleton instance = null; public static Singleton getInstance() { if (instance == null) { // ① synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); // ② } } } return instance; }}以上问题发生在代码 ② 这一行“instance = new Singleton();”,这行代码看似只是一个创建对象的过程,然而它的实际执行却分为以下 3 步:创建内存空间。在内存空间中初始化对象 Singleton。将内存地址赋值给 instance 对象(执行了此步骤,instance 就不等于 null 了)。如果此变量不加 volatile,那么线程 1 在执行到上述代码的第 ② 处时就可能会执行指令重排序,将原本是 1、2、3 的执行顺序,重排为 1、3、2。但是特殊情况下,线程 1 在执行完第 3 步之后,如果来了线程 2 执行到上述代码的第 ① 处,判断 instance 对象已经不为 null,但此时线程 1 还未将对象实例化完,那么线程 2 将会得到一个被实例化“一半”的对象,从而导致程序执行出错,这就是为什么要给私有变量添加 volatile 的原因了。要使以上单例模式变为线程安全的程序,需要给 instance 变量添加 volatile 修饰,它的最终实现代码如下:public class Singleton { private Singleton() {} // 使用 volatile 禁止指令重排序 private static volatile Singleton instance = null; // 【主要是此行代码发生了变化】 public static Singleton getInstance() { if (instance == null) { // ① synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); // ② } } } return instance; }}2.volatile 实现原理volatile 实现原理和它的作用有关,我们首先先来看它的内存可见性。2.1 内存可见性实现原理volatile 内存可见性主要通过 lock 前缀指令实现的,它会锁定当前内存区域的缓存(缓存行),并且立即将当前缓存行数据写入主内存(耗时非常短),回写主内存的时候会通过 MESI 协议使其他线程缓存了该变量的地址失效,从而导致其他线程需要重新去主内存中重新读取数据到其工作线程中。什么 MESI 协议?MESI 协议,全称为 Modified, Exclusive, Shared, Invalid,是一种高速缓存一致性协议。它是为了解决多处理器(CPU)在并发环境下,多个 CPU 缓存不一致问题而提出的。MESI 协议定义了高速缓存中数据的四种状态:Modified(M):表示缓存行已经被修改,但还没有被写回主存储器。在这种状态下,只有一个 CPU 能独占这个修改状态。Exclusive(E):表示缓存行与主存储器相同,并且是主存储器的唯一拷贝。这种状态下,只有一个 CPU 能独占这个状态。Shared(S):表示此高速缓存行可能存储在计算机的其他高速缓存中,并且与主存储器匹配。在这种状态下,各个 CPU 可以并发的对这个数据进行读取,但都不能进行写操作。Invalid(I):表示此缓存行无效或已过期,不能使用。MESI 协议的主要用途是确保在多个 CPU 共享内存时,各个 CPU 的缓存数据能够保持一致性。当某个 CPU 对共享数据进行修改时,它会将这个数据的状态从 S(共享)或 E(独占)状态转变为 M(修改)状态,并等待适当的时机将这个修改写回主存储器。同时,它会向其他 CPU 广播一个“无效消息”,使得其他 CPU 将自己缓存中对应的数据状态转变为I(无效)状态,从而在下次访问这个数据时能够从主存储器或其他 CPU 的缓存中重新获取正确的数据。这种协议可以确保在多处理器环境中,各个 CPU 的缓存数据能够正确、一致地反映主存储器中的数据状态,从而避免由于缓存不一致导致的数据错误或程序异常。2.2 有序性实现原理volatile 的有序性是通过插入内存屏障(Memory Barrier),在内存屏障前后禁止重排序优化,以此实现有序性的。什么是内存屏障?内存屏障(Memory Barrier 或 Memory Fence)是一种硬件级别的同步操作,它强制处理器按照特定顺序执行内存访问操作,确保内存操作的顺序性,阻止编译器和 CPU 对内存操作进行不必要的重排序。内存屏障可以确保跨越屏障的读写操作不会交叉进行,以此维持程序的内存一致性模型。在 Java 内存模型(JMM)中,volatile 关键字用于修饰变量时,能够保证该变量的可见性和有序性。关于有序性,volatile 通过内存屏障的插入来实现:写内存屏障(Store Barrier / Write Barrier): 当线程写入 volatile 变量时,JMM 会在写操作前插入 StoreStore 屏障,确保在这次写操作之前的所有普通写操作都已完成。接着在写操作后插入 StoreLoad 屏障,强制所有后来的读写操作都在此次写操作完成之后执行,这就确保了其他线程能立即看到 volatile 变量的最新值。读内存屏障(Load Barrier / Read Barrier): 当线程读取 volatile 变量时,JMM 会在读操作前插入 LoadLoad 屏障,确保在此次读操作之前的所有读操作都已完成。而在读操作后插入 LoadStore 屏障,防止在此次读操作之后的写操作被重排序到读操作之前,这样就确保了对 volatile 变量的读取总是能看到之前对同一变量或其他相关变量的写入结果。通过这种方式,volatile 关键字有效地实现了内存操作的顺序性,从而保证了多线程环境下对 volatile 变量的操作遵循 happens-before 原则,确保了并发编程的正确性。2.3 简单回答因为内存屏障的作用既能保证内存可见性,同时又能禁止指令重排序。因此你也可以笼统的回答 volatile 是通过内存屏障实现的。但是,回答的越细,面试的成绩越高,面试的通过率也就越高。课后思考什么是 happens-before 原则?除了 synchronized、ReentrantLock 和 volatile 之外,并发编程中还有哪些常见的关键字呢?它们背后的实现原理又是什么呢?参考 & 鸣谢javacn.site
点赞 6
评论 1
全部评论
推荐
最新
楼层
暂无评论,快来抢首评~
相关推荐
昨天 12:26
河北大学 产品经理
毕业之后,大家就会回到自己的阶级
我朋友圈的同学们出国旅游,看演唱会,大厂实习,考研上岸......活得风生水起。可是我在这一年里几乎从朋友圈消失了,我经历了实习失败,秋招失败,省考失败,春招失败,丝毫没有放松的时刻。一方面还在为毕业论文和学校课业焦头烂额,一方面还要被父母埋怨天天待在学校里不去上班无所事事。父母是普通人,亲戚里也没有人脉,没有人为我兜底。我天天为毕业以后的生计发愁,过着朝不保夕的日子,活得好累。虽然毕业即失业对我来说绝不是一句虚言,但是此刻我只想赶紧毕业,不论今后做什么,我都想赶紧消失于人海,自己努力地活下去。
真烦好烦真烦:
其实在学校也有很大区别,家庭好的同学有更大的胆识,因为有家庭托底,而家里困难的人则多数畏畏缩缩
如果不工作真的会快乐吗
点赞
评论
收藏
分享
04-25 10:28
海康威视_算法研发部_AI算法工程师(准入职员工)
海康内推-海康内推码
岗位:武汉 嵌入式开发timeline:8.30 测评,10.9 一面,10.11 二面一面技术:自我介绍讲下实习经历平常怎么debug项目中有没有遇到什么问题,怎么解决的介绍一下学校经历了解数据结构吗?基本的数据结构?查找搜索效率?有什么方法可以提高效率?具体围绕数据结构问了很多RTOS,多任务操作反问:做存储固件的,具体的进来再分二面HR:北京线下,地点在北京研发中心,具体内容就是唠家常,一些HR面的基本问题销售工程师工作体验,总结累但成长很多。1.大家最先关注的就是HIK的工作压力,只能说体面厂没有辜负盛名!名不虚传!我来这边是销售岗,基本上每天都要差不多11点下班。因为销售不仅需要对接...
点赞
评论
收藏
分享
03-11 02:16
武汉生物工程学院 Java
25 0 offer😭😭😭到底要怎么才能拿到offer啊,深夜emo了
群星之怒:
1.照片可以换更好一点的,可以适量P图,带一些发型,遮住额头,最好穿的正式一点,可以适当P图。2.内容太少。建议添加的:求职意向(随着投递岗位动态更改);项目经历(内容太少了建议添加一些说明,技术栈:用到了什么技术,还有你是怎么实现的,比如如何确保数据传输稳定的,角色注册用到了什么技术等等。)项目经历是大头,没有实习是硬伤,如果项目经理不突出的话基本很难过简历筛。3.有些内容不必要,比如自我评价,校内实践。如果实践和工作无关千万别写,不如多丰富丰富项目。4.排版建议:建议排版是先基础信息,然后教育背景(要突出和工作相关的课程),然后专业技能(一定要简短,不要长篇大论,写你会什么,会的程度就可以),然后是项目经历(一定要详细,占整个简历一定要超过一半,甚至超过百分之70都可以)。最后如果有一部分空白的话可以填补上校内获得的专业相关的奖项,没有就写点校园经历和自我评价。5.技术一定要够硬,禁得住拷打。还有作息尽量保证正常,不要太焦虑。我24双非本科还是非科班,秋招春招各找了一段实习结果都没有转正,当时都想噶了,最后6月份在校的尾巴也找到一份工作干到现在,找工作有时很看运气的不要急着自我否定。 加油
点赞
评论
收藏
分享
03-31 15:04
黑龙江科技大学 Java
每日一绷
牛客10001:
问就是六个月,全国可飞,给钱就干
点赞
评论
收藏
分享
04-25 16:17
三环集团有限公司_市场策略研究(准入职员工)
三环集团内推-三环集团内推码
机电面试流程一面(技术面)面试官:两位,一位是机电部门的技术主管,另一位是资深工程师。开场:面试官先让我进行了 3 - 5 分钟的自我介绍,主要围绕学习背景、项目经验以及对机电岗位的理解。项目提问:接着针对我简历上写的一个机电设备改造项目展开提问,比如改造的原因、具体实施过程中遇到了哪些技术难题,是怎么解决的。我详细讲述了因为原设备能耗高、效率低,所以我们通过更换节能型电机、优化控制系统来解决问题。在解决技术难题时,我们查阅了大量资料,请教了学校的教授,最终采用了新的控制算法实现了预期效果。专业知识考查:问了一些关于电机正反转控制电路的原理,以及如何实现电机的调速,还有 PLC 常见的故障及排...
三环集团开奖34人在聊
点赞
评论
收藏
分享
评论
点赞成功,聊一聊 >
点赞
收藏
分享
评论
提到的真题
返回内容
全站热榜
更多
1
...
暑期后端高频问题汇总
6.8W
2
...
左手敲代码的程序员,不配拥有offer吗?
5.7W
3
...
想听实话吗,校招ssp聊聊大厂客户端
2.9W
4
...
大连某小区保安一面
2.7W
5
...
后端简历上最值得写的项目
8797
6
...
北京到底有谁在啊?
8611
7
...
五一假期,弯道超车时间表
7476
8
...
美团/饿了么/京东 配送端面经
6245
9
...
暑期实习终章
6082
10
...
五一准备刷完这些面试八股题!!
5885
创作者周榜
更多
正在热议
更多
#
找工作,行业重要还是岗位重要?
#
6538次浏览
84人参与
#
盲审过后你想做什么?
#
12231次浏览
107人参与
#
五一之后,实习真的很难找吗?
#
43808次浏览
311人参与
#
领导秒批的请假话术
#
9441次浏览
72人参与
#
安克创新求职进展汇总
#
32452次浏览
412人参与
#
如果不工作真的会快乐吗
#
100787次浏览
860人参与
#
每人推荐一个小而美的高薪公司
#
72801次浏览
1357人参与
#
京东工作体验
#
12936次浏览
90人参与
#
五一假期,你打算“躺”还是“卷”?
#
24382次浏览
386人参与
#
考研可以缓解求职焦虑吗
#
20299次浏览
241人参与
#
如何缓解入职前的焦虑
#
171518次浏览
1267人参与
#
面试等了一周没回复,还有戏吗
#
115111次浏览
1072人参与
#
找工作前vs找工作后的心路变化
#
7081次浏览
64人参与
#
应届生薪资多少才合理?
#
3028次浏览
24人参与
#
写简历别走弯路
#
713976次浏览
7848人参与
#
你喜欢工作还是上学
#
37236次浏览
407人参与
#
如果有时光机,你最想去到哪个年纪?
#
43151次浏览
765人参与
#
牛友们的论文几号送审
#
27115次浏览
622人参与
#
扒一扒那些奇葩实习经历
#
41409次浏览
770人参与
#
24届的你们现状如何了?
#
64461次浏览
377人参与
牛客网
牛客企业服务