面试官最爱的volatile 关键字,这些问题你都搞懂了没?

文章推薦指數: 80 %
投票人數:10人

volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读。

传递性:如果(A)happens-before(B),且(B)happens-before(C),那么(A ... 开源基础软件社区 首页贴子问答资源开源课堂直播发现登录/注册51CTO中国优质的IT技术网站51CTO博客专业IT技术创作平台51CTO学堂IT职业在线教育平台专栏极客Show鸿蒙技术特刊 面试官最爱的volatile关键字,这些问题你都搞懂了没? lanhy发布于2020-9-111:07浏览0收藏前言volatile相关的知识点,在面试过程中,属于基础问题,是必须要掌握的知识点,如果回答不上来会严重扣分的哦。

volatile关键字基本介绍volatile可以看成是synchronized的一种轻量级的实现,但volatile并不能完全代替synchronized,volatile有synchronized可见性的特性,但没有synchronized原子性的特性。

可见性即用volatile关键字修饰的成员变量表明该变量不存在工作线程的副本,线程每次直接都从主内存中读取,每次读取的都是最新的值,这也就保证了变量对其他线程的可见性。

另外,使用volatile还能确保变量不能被重排序,保证了有序性。

当一个变量定义为volatile之后,它将具备两种特性:保证此变量对所有线程的可见性禁止指令重排序优化volatile与synchronized的区别:1、volatile只能修饰实例变量和类变量,而synchronized可以修饰方法,以及代码块。

2、volatile保证数据的可见性,但是不保证原子性;而synchronized是一种排他(互斥)的机制,既保证可见性,又保证原子性。

3、volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。

4、volatile可以看做是轻量版的synchronized,volatile不保证原子性,但是如果是对一个共享变量进行多个线程的赋值,而没有其他的操作,那么就可以用volatile来代替synchronized,因为赋值本身是有原子性的,而volatile又保证了可见性,所以就可以保证线程安全了。

保证此变量对所有线程的可见性:当一条线程修改了这个变量的值,新值对于其他线程可以说是可以立即得知的。

Java内存模型规定了所有的变量都存储在主内存,每条线程还有自己的工作内存,线程的工作内存保存了该线程使用到的变量在主内存的副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读取主内存中的变量。

知识拓展:内存可见性: 概念:JVM内存模型:主内存和线程独立的工作内存。

Java内存模型规定,对于多个线程共享的变量,存储在主内存当中,每个线程都有自己独立的工作内存(比如CPU的寄存器),线程只能访问自己的工作内存,不可以访问其它线程的工作内存。

工作内存中保存了主内存共享变量的副本,线程要操作这些共享变量,只能通过操作工作内存中的副本来实现,操作完毕之后再同步回到主内存当中。

如何保证多个线程操作主内存的数据完整性是一个难题,Java内存模型也规定了工作内存与主内存之间交互的协议,定义了8种原子操作:lock:将主内存中的变量锁定,为一个线程所独占。

unclock:将lock加的锁定解除,此时其它的线程可以有机会访问此变量。

read:将主内存中的变量值读到工作内存当中。

 load:将read读取的值保存到工作内存中的变量副本中。

use:将值传递给线程的代码执行引擎。

assign:将执行引擎处理返回的值重新赋值给变量副本。

store:将变量副本的值存储到主内存中。

write:将store存储的值写入到主内存的共享变量当中。

通过上面Java内存模型的概述,我们会注意到这么一个问题,每个线程在获取锁之后会在自己的工作内存来操作共享变量,操作完成之后将工作内存中的副本回写到主内存,并且在其它线程从主内存将变量同步回自己的工作内存之前,共享变量的改变对其是不可见的。

即其他线程的本地内存中的变量已经是过时的,并不是更新后的值。

volatile保证可见性的原理是在每次访问变量时都会进行一次刷新,因此每次访问都是主内存中最新的版本。

所以volatile关键字的作用之一就是保证变量修改的实时可见性。

即,volatile的特殊规则就是: read、load、use动作必须连续出现。

assign、store、write动作必须连续出现。

所以,使用volatile变量能够保证: 每次读取前必须先从主内存刷新最新的值。

每次写入后必须立即同步回主内存当中。

也就是说,volatile关键字修饰的变量看到的是自己的最新值。

线程1中对变量v的最新修改,对线程2是可见的。

禁止指令重排序优化: volatilebooleanisOK=false; //假设以下代码在线程A执行 A.init(); isOK=true; //假设以下代码在线程B执行 while(!isOK){ sleep(); } B.init(); A线程在初始化的时候,B线程处于睡眠状态,等待A线程完成初始化的时候才能够进行自己的初始化。

这里的先后关系依赖于isOK这个变量。

如果没有volatile修饰isOK这个变量,那么isOK的赋值就可能出现在A.init()之前(指令重排序,Java虚拟机的一种优化措施),此时A没有初始化,而B的初始化就破坏了它们之前形成的那种依赖关系,可能就会出错。

知识拓展:指令重排序: 概念:指令重排序是JVM为了优化指令,提高程序运行效率,在不影响单线程程序执行结果的前提下,尽可能地提高并行度。

编译器、处理器也遵循这样一个目标。

注意是单线程。

多线程的情况下指令重排序就会给程序带来问题。

不同的指令间可能存在数据依赖。

比如下面的语句:  intl=3;//(1) intw=4;//(2) ints=l*w;//(3)  面积的计算依赖于l与w两个变量的赋值指令。

而l与w无依赖关系。

重排序会遵守两个规则: as-if-serial规则:as-if-serial规则是指不管如何重排序(编译器与处理器为了提高并行度),(单线程)程序的结果不能被改变。

这是编译器、Runtime、处理器必须遵守的语义。

happens-before规则:程序顺序规则:一个线程中的每个操作,happens-before于线程中的任意后续操作。

监视器锁规则:一个锁的解锁,happens-before于随后对这个锁的加锁。

volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读。

传递性:如果(A)happens-before(B),且(B)happens-before(C),那么(A)happens-before(C)。

线程start()规则:主线程A启动线程B,线程B中可以看到主线程启动B之前的操作。

也就是start()happens-before线程B中的操作。

线程join()规则:主线程A等待子线程B完成,当子线程B执行完毕后,主线程A可以看到线程B的所有操作。

也就是说,子线程B中的任意操作,happens-beforejoin()的返回。

中断规则:一个线程调用另一个线程的interrupt,happens-before于被中断的线程发现中断。

终结规则:一个对象的构造函数的结束,happens-before于这个对象finalizer的开始。

概念:前一个操作的结果可以被后续的操作获取。

讲直白点就是前面一个操作把变量a赋值为1,那后面一个操作肯定能知道a已经变成了1。

happens-before(先行发生)规则如下:虽然,(1)-happensbefore->(2),(2)-happensbefore->(3),但是计算顺序(1)(2)(3)与(2)(1)(3)对于l、w、area变量的结果并无区别。

编译器、Runtime在优化时可以根据情况重排序(1)与(2),而丝毫不影响程序的结果。

volatile使用场景:1、对变量的写操作不依赖当前变量的值。

2、该变量没有包含在其他变量的不变式中。

如果正确使用volatile的话,必须依赖下以下种条件:也可以这样理解,就是上面的2个条件需要保证操作是原子性操作,才能保证使用volatile关键字的程序在并发时能够正确执行。

第一个条件的限制使volatile变量不能用作线程安全计数器。

虽然增量操作(i++)看上去类似一个单独操作,实际上它是一个由(读取-修改-写入)操作序列组成的组合操作,必须以原子方式执行,而volatile不能提供必须的原子特性。

实现正确的操作需要使i的值在操作期间保持不变,而volatile变量无法实现这点。

在以下两种情况下都必须使用volatile:1、状态的改变。

2、读多写少的情况。

具体如下: //场景一:状态改变 /** *双重检查(DCL) */ publicclassSun{ privatestaticvolatileSunsunInstance; privateSun(){ } publicstaticSungetSunInstance(){ if(sunInstance==null){ synchronized(Sun.class){ if(sunInstance==null){ sunInstance=newSun(); } } } returnsunInstance; } } //场景二:读多写少 publicclassVolatileTest{ privatevolatileintvalue; //读操作,没有synchronized,提高性能 publicintgetValue(){ returnvalue; } //写操作,必须synchronized。

因为x++不是原子操作 publicsynchronizedintincrement(){ returnvalue++; } } 问题来了,volatile是如何防止指令重排序优化的呢?答: volatile关键字通过“内存屏障”的方式来防止指令被重排序,为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。

大多数的处理器都支持内存屏障的指令。

对于编译器来说,发现一个最优布置来最小化插入屏障的总数几乎不可能,为此,Java内存模型采取保守策略。

下面是基于保守策略的JMM内存屏障插入策略: 在每个volatile写操作的前面插入一个StoreStore屏障。

在每个volatile写操作的后面插入一个StoreLoad屏障。

在每个volatile读操作的后面插入一个LoadLoad屏障。

在每个volatile读操作的后面插入一个LoadStore屏障。

知识拓展:内存屏障: 内存屏障(MemoryBarrier,或有时叫做内存栅栏,MemoryFence)是一种CPU指令,用于控制特定条件下的重排序和内存可见性问题。

Java编译器也会根据内存屏障的规则禁止重排序。

内存屏障可以被分为以下几种类型: LoadLoad屏障:对于这样的语句Load1;LoadLoad;Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。

StoreStore屏障:对于这样的语句Store1;StoreStore;Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。

LoadStore屏障:对于这样的语句Load1;LoadStore;Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。

StoreLoad屏障:对于这样的语句Store1;StoreLoad;Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。

它的开销是四种屏障中最大的。

在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能。

 分类框架语言标签java已于2020-9-218:13:58修改 赞 收藏回复分享微博QQ微信 举报 举报举报取消微信扫码分享删除帖子删除取消回复添加资源 添加资源将有机会获得更多曝光,你也可以直接关联已上传资源去关联添加资源取消确定相关推荐面试官最爱的volatile关键字,这些问题你都搞懂了没?langrisser•3074浏览•0回复Java并发--volatile关键字lanhy•3507浏览•0回复什么时候使用volatile关键字?蓝月亮•3834浏览•0回复你确定这些鸿蒙相关的问题,你都知道?开源基础软件社区官方•2798浏览•1回复Java中的volatile关键字最全总结(四)pivoteic•162浏览•0回复Java中的volatile关键字最全总结(三)pivoteic•161浏览•0回复Java中的volatile关键字最全总结(二)pivoteic•171浏览•0回复Java中的volatile关键字最全总结(一)pivoteic•101浏览•0回复HarmonyOSAI基础技术赋能之关键字获取软通动力HOS•1.4w浏览•6回复面试官常问的垃圾回收器,这次全搞懂footballboy•3313浏览•0回复Java-技术专题-synchronized关键字lanhy•1480浏览•0回复Java并发--final关键字lanhy•1807浏览•0回复Java并发-Synchronized关键字kcoufee•3588浏览•0回复面试官:你说说一条查询SQL的执行过程?wg204wg•123浏览•0回复JavaScript中的这些继承方式,你弄懂了吗?detailtoo•1417浏览•0回复Java-技术专题-final关键字bugouhen•3363浏览•0回复Java的这些能力你都学会了吗laojean•3909浏览•0回复Java-技术专题-final关键字lanhy•2711浏览•0回复MySQL┃多个角度说明sql优化,让你吊打面试官!ImCrow•384浏览•0回复C++中重要关键字总结kekenai•2692浏览•0回复SQLServer优化:SQLServer中NOLOCK关键字的用法介绍tomcatf149999•236浏览•0回复面试官:Redis的事务满足原子性吗?IIIIJDASHJF•306浏览•0回复《吊打面试官》系列-Redis基础mike_hit•189浏览•0回复面试官:Kafka如何优化内存缓冲机制造成的频繁GC问题?yjfhd•542浏览•0回复《吊打面试官》系列-Redis常见面试题mike_hit•443浏览•0回复这个用户很懒,还没有个人简介帖子视频声望粉丝私信关注 最近发布一文读懂开源项目OpenHarmony2021-06-0810:13:32发布华为鸿蒙系统确认适配高通/联发科手机!曝OV魅族有意采用2021-05-0810:55:26发布热门推荐【星光计划3.0】夏日挑战赛,更文瓜分奖励金!92回复直播公开课|单片机开发过程中的调试绝招83回复#云原生征文#自建流媒体服务,快速打造自己的短视频点播平台5回复#基于DAYU200开发一个好玩的图传智能小车--demo样例#5回复#云原生征文#如何为项目构建高效的统一文件存储方案5回复相关问题JavaText控件,如何设置字间距?1回答金融类的智能手表App,个人开发者该如何过审??(终于搞懂了手表负一屏消息)1回答在学习鸿蒙开发过程中,你遇到了哪些问题?7回答关于JavaUICheckbox的问题?1回答鸿蒙是发展java还是js,难道他们想两个都维护吗?1回答 上一篇: Java并发--volatile关键字 下一篇: java事务传播机制一文搞懂。

社区精华内容目录Copyright©2005-202251CTO.COM京ICP证060544版权所有未经许可请勿转载精选客服订阅鸿蒙技术特刊,精选内容抢先看微信扫码关注,即刻订阅



請為這篇文章評分?