Java面试官最爱问的volatile关键字

在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程序在各种平台上都能达到一致的内存访问效果。

JMM

Java内存模型是通过变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值,将主内存作为传递媒介。可举例说明内存可见性的过程。

JMM

本地内存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
12
13
14
15
16

在上面的代码中,单线程执行时,read方法能够获得flag的值进行判断,获得预期结果。但在多线程的情况下就可能出现不同的结果。比如,当线程A进行write操作时,由于指令重排,write方法中的代码执行顺序可能会变成下面这样:

flag = true; //2
a = 1; //1

1
2

也就是说可能会先对flag赋值,然后再对a赋值。这在单线程中并不影响最终输出的结果。

但如果与此同时,B线程在调用read方法,那么就有可能出现flag为true但a还是0,这时进入第4步操作的结果就为0,而不是预期的1了。

而volatile关键词修饰的变量,会禁止指令重排的操作,从而在一定程度上避免了多线程中的问题。
面试官:volatile能保证原子性吗?

volatile保证了可见性和有序性(禁止指令重排),那么能否保证原子性呢?

volatile不能保证原子性,它只是对单个volatile变量的读/写具有原子性,但是对于类似i 这样的复合操作就无法保证了。

如下代码,从直观上来讲,感觉输出结果为10000,但实际上并不能保证,就是因为inc 操作属于复合操作。

public class Test {
public volatile int inc = 0;

public void increase() {
    inc  ;
}

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<1000;j  )
                    test.increase();
            };
        }.start();
    }

    while(Thread.activeCount()>1)  //保证前面的线程都执行完
        Thread.yield();
    System.out.println(test.inc);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

假设线程A,读取了inc的值为10,然被阻塞,因未对变量进行修改,未触发volatile规则。线程B此时也读取inc的值,主存里inc的值依旧为10,做自增,然后立刻写回主存,值为11。此时线程A执行,由于工作内存里保存的是10,所以继续做自增,再写回主存,11又被写了一遍。所以虽然两个线程执行了两次increase(),结果却只加了一次。

有人说,volatile不是会使缓存行无效的吗?但是这里线程A读取之后并没有修改inc值,线程B读取时依旧是10。又有人说,线程B将11写回主存,不会把线程A的缓存行设为无效吗?只有在做读取操作时,发现自己缓存行无效,才会去读主存的值,而线程A的读取操作在线程B写入之前已经做过了,所以这里线程A只能继续做自增了。

针对这种情况,只能使用synchronized、Lock或并发包下的atomic的原子操作类。
面试官:刚提到synchronized,能说说它们之间的区别吗

volatile本质是在告诉JVM当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
volatile仅能使用在变量级别;synchronized则可以使用在变量、方法和类级别的;
volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性;
volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。

面试官:还能举出其他例子说明volatile的作用吗

可举单例模式的实现,典型的双重检查锁定(DCL):

class Singleton{
private volatile static Singleton instance = null;

private Singleton() {}

public static Singleton getInstance() {
    if(instance==null) { // 1
        synchronized (Singleton.class) {
            if(instance==null)
                instance = new Singleton(); // 2
        }
    }
    return instance;
}

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

这是一种懒汉的单例模式,使用时才创建对象,而且为了避免初始化操作的指令重排序,给instance加上了volatile。

为什么用了synchronized还要用volatile?具体来说就是synchronized虽然保证了原子性,但却没有保证指令重排序的正确性,会出现A线程执行初始化,但可能因为构造函数里面的操作太多了,所以A线程的instance实例还没有造出来,但已经被赋值了(即代码中2操作,先分配内存空间后构建对象)。http://www.daiqiyang.com

而B线程这时过来了(代码1操作,发现instance不为null),错以为instance已经被实例化出来,一用才发现instance尚未被初始化。要知道我们的线程虽然可以保证原子性,但程序可能是在多核CPU上执行。
小结

当然,针对volatile关键字还有其他方面的拓展,比如讲到JMM时可拓展到JMM与Java内存模型的区别,讲到原子性时可扩展到如何查看class字节码,讲到并发可扩展到线程并发的方法面面。
————————————————
版权声明:本文为CSDN博主「二师兄-公众号-程序新视界」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/wo541075754/article/details/102968894

原文地址:https://blog.51cto.com/6624835/2462931

时间: 2024-09-29 03:43:51

Java面试官最爱问的volatile关键字的相关文章

一线大厂面试官最喜欢问的15道Java多线程面试题

前言 在任何Java面试当中多线程和并发方面的问题都是必不可少的一部分.如果你想获得更多职位,那么你应该准备很多关于多线程的问题. 他们会问面试者很多令人混淆的Java线程问题.面试官只是想确信面试者有足够的Java线程与并发方面的知识,因为候选人中有很多只浮于表面.现在引用Java5并发包关于并发工具和并发集合的问题正在增多.那些问题中ThreadLocal.Blocking Queue.Counting Semaphore和ConcurrentHashMap比较流行. Java多线程面试题及

一个资深java面试官的“面试心得”

在公司当技术面试官几年间,从应届生到工作十几年的应聘者都遇到过.先表达一下我自己对面试的观点: 1.笔试.面试去评价一个人肯定是不够准确的,了解一个人最准确的方式就是“路遥知马力,日久见人心”.通过一.二个小时内的做题.交流,只是没有其他办法下进行的无奈之举,所以通过了面试不代表有多成功,没通过也不代表有多失败.2.好的面试官本身交谈的时候就不应当把自己一个居高临下的角色上,应当把自己和应聘者当做两个做技术的人平等的交流,把自己当作权威往往就会受到观点的角度.语言表达.工作领域的惯性的制约.3.

java面试官如何面试别人

                                                                                  java面试官如何面试别人(一) java面试官的"面试心得" 在公司当技术面试官几年间,从应届生到工作十几年的应聘者都遇到过.先表达一下我自己对面试的观点: 1.笔试.面试去评价一个人肯定是不够准确的,了解一个人最准确的方式就是"路遥知马力,日久见人心".通过一.二个小时内的做题.交流,只是没有其他办法

阿里Java面试官分享初级Java程序员通过面试的技巧

本来想分享毕业生和初级程序员如何进大公司的经验,但后来一想,人各有志,有程序员或许想进成长型或创业型公司或其它类型的公司,所以就干脆来分享些提升技能和通过面试的技巧,技巧我讲,公司你选,两厢便利. 毕业生和初级程序员(一般是工作经验3年以下)大多处于事业的青黄不接的阶段,在找工作时往往会遇到缺乏实际项目经验的瓶颈,作为技术面试官,我也经常在面试过程中感受到这些候选人缺乏实际经验的缺陷.不过本人之前做过java兼职培训老师,也总结了些这批人群提升实际技能和面试技能的技巧,最近也老有人来问我这个,所

python web为什么要学数据结构与算法面试官为什么总问那几个算法和线性表二叉树

算法是什么? 举个简单的例子 当年刘景生病危刘琦被卷入继位之事 无奈求助于诸葛亮 以上古兵书为由 邀孔明阁楼相谈.... 诸葛亮曾说 那部阵书开篇就是阵法源自于心法 所以古代军师进步也是看兵法 算法也是同理 虽然我们作为web后端开发人员 但是每天所见所闻日子久也就适应了. 但是想要再进一步 两种方法 一 多看高质量代码 二 看算法 我们作为web开发可能算法运用的不是很多 每天也就是写接口调接口和产品经理撕 但是算法最重要的地方是会帮你扩充你的逻辑思维 可能刚看算法的人认为太难了我真学不来 举

【JAVA秒会技术之秒杀面试官】秒杀Java面试官——集合篇(一)

[JAVA秒会技术之秒杀面试官]秒杀Java面试官--集合篇(一) [JAVA秒会技术之秒杀面试官]JavaEE常见面试题(三) http://blog.csdn.net/qq296398300/article/category/6876287

求职Python开发,面试官最喜欢问的几个问题

关注公众号「Python专栏」,后台回复:简历模板,获取给大家准备的价值99元的程序员简历模版11份. ![Python专栏二维码](https://img-blog.csdnimg.cn/20190228220401806.jpeg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTIzNjU4Mjg=,size_4,color_FFFF

求职Python开发,面试官最喜欢问的9个问题【附答案】

0. 概述 Python是个非常受欢迎的编程语言,随着近些年机器学习.云计算等技术的发展,Python的职位需求越来越高.下面我收集了一些Python面试官经常问的问题,供大家参考学习. 相信大家在学python的时候肯定会遇到很多难题,以及对于新技术的追求,这里推荐一下我们的Python学习扣qun:784758214,这里是python学习者聚集地!!同时,自己是一名高级python开发工程师,从基础的python脚本到web开发.爬虫.django.数据挖掘等,零基础到项目实战的资料都有整

Java并发编程(六):volatile关键字解析(转载)

原文转载自:http://www.cnblogs.com/dolphin0520/p/3920373.html volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在Java 5之后,volatile关键字才得以重获生机. volatile关键字虽然从字面上理解起来比较简单,但是要用好不是一件容易的事情.由于volatile关键字是与Java的内存模型有关的,因此在讲述volatile关键之前,我