在Java的面试当中,面试官最爱问的就是volatile关键字相关的问题。经过多次面试之后,你是否思考过,为什么他们那么爱问volatile关键字相关的问题?而对于你,如果作为面试官,是否也会考虑采用volatile关键字作为切入点呢?
为什么爱问volatile关键字
爱问volatile关键字的面试官,大多数情况下都是有一定功底的,因为volatile作为切入点,往底层走可以切入Java内存模型(JMM),往并发方向走又可接切入Java并发编程,当然,再深入追究,JVM的底层操作、字节码的操作、单例都可以牵扯出来。
所以说懂的人提问题都是有门道的。那么,先整体来看看volatile关键字都设计到哪些点:内存可见性(JMM特性)、原子性(JMM特性)、禁止指令重排、线程并发、与synchronized的区别……再往深层次挖,可能就涉及到字节码、JVM等。
不过值得庆幸的是,如果你已经学习了微信公众号“程序新视界”JVM系列的文章,上面的知识点已经不是什么问题了,权当是复习了。那么,下面就以面试官提问的形式,在不看答案的情况下,尝试回答,看看学习效果如何。夺命连环问,开始……
面试官:说说volatile关键字的特性
被volatile修饰的共享变量,就具有了以下两点特性:
- 保证了不同线程对该变量操作的内存可见性;
- 禁止指令重排序;
回答的很好,点出了volatile关键字两大特性。针对该两大特性继续深入。
面试官:什么是内存可见性?能否举例说明?
该问题涉及到Java内存模型(JVM)和它的内存可见性特性,这里将前面系列《Java内存模型(JMM)详解》和《Java内存模型相关原则详解》中的部分内容整理出来回答。
先说内存模型:Java虚拟机规范试图定义一种Java内存模型(JMM),来屏蔽掉各种硬件和操作系统的内存访问差异,让Java程序在各种平台上都能达到一致的内存访问效果。
Java内存模型是通过变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值,将主内存作为传递媒介。可举例说明内存可见性的过程。
本地内存A和B有主内存中共享变量x的副本,初始值都为0。线程A执行之后把x更新为1,存放在本地内存A中。当线程A和线程B需要通信时,线程A首先会把本地内存中x=1值刷新到主内存中,主内存中的x值变为1。随后,线程B到主内存中去读取更新后的x值,线程B的本地内存的x值也变为了1。
最后再说可见性:可见性是指当一个线程修改了共享变量的值,其他线程能够立即得知这个修改。
无论普通变量还是volatile变量都是如此,只不过volatile变量保证新值能够立马同步到主内存,使用时也立即从主内存刷新,保证了多线程操作时变量的可见性。而普通变量不能够保证。
面试官:提到JMM和可见性,能说说JMM的其他特性吗
我们知道JMM除了可见性,还有原子性和有序性。
原子性即一个操作或一系列是不可中断的。即使是在多个线程的情况下,操作一旦开始,就不会被其他线程干扰。
比如,对于一个静态变量int x两条线程同时对其赋值,线程A赋值为1,而线程B赋值为2,不管线程如何运行,最终x的值要么是1,要么是2,线程A和线程B间的操作是没有干扰的,这就是原子性操作,不可被中断的。
在Java内存模型中有序性可归纳为这样一句话:如果在本线程内观察,所有操作都是有序的,如果在一个线程中观察另一个线程,所有操作都是无序的。
有序性是指对于单线程的执行代码,执行是按顺序依次进行的。但在多线程环境中,则可能出现乱序现象,因为在编译过程会出现“指令重排”,重排后的指令与原指令的顺序未必一致。
因此,上面归纳的前半句指的是线程内保证串行语义执行,后半句则指指“令重排现”象和“工作内存与主内存同步延迟”现象。
面试官:你多次提到指令重排,能举例说明吗?
CPU和编译器为了提升程序执行的效率,会按照一定的规则允许进行指令优化。但代码逻辑之间是存在一定的先后顺序,并发执行时按照不同的执行逻辑会得到不同的结果。
举个例说明多线程中可能出现的重排现象:
class ReOrderDemo { int a = 0; boolean flag = false;
public void write() { a = 1; //1 flag = true; //2 } public void read() { if (flag) { //3 int i = a * a; //4 …… } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 1
- 2
- 3