在Java相关的职位面试中,很多Java面试官都喜欢考察应聘者对Java并发的了解程度,以volatile关键字为切入点,往往会问到底,Java内存模型(JMM)和Java并发编程的一些特点都会被牵扯出来,再深入的话还会考察JVM底层实现以及操作系统的相关知识。

接下来让我们在一个假想的面试过程中来学习一下volitile关键字吧。

1. Java并发这块掌握的怎么样?来谈谈你对volatile关键字的理解吧。

参考答案:

我的理解是,被volatile修饰的共享变量,就会具有以下两个特性:

  1. 保证了不同线程对该变量操作的内存可见性。
  2. 禁止指令重排序。

2. 那你可不可以详细的说一下究竟什么是内存可见性,什么又是重排序?

参考答案:

这个要是说起来可就多了,我就从Java内存模型开始说起吧。Java虚拟机规范试图定义一个Java内存模型(JMM),以屏蔽所有类型的硬件和操作系统内存访问差异,让Java程序在不同的平台上能够达到一致的内存访问效果。简单地说,由于CPU执行指令的速度很快,但是内存访问速度很慢,差异不是一个量级,所以搞处理器的那群大佬们又在CPU里加了好几层高速缓存。

在Java内存模型中,对上述优化进行了一波抽象。JMM规定所有的变量都在主内存中,类似于上面提到的普通内存,每个线程又包含自己的工作内存,为了便于理解可以看成CPU上的寄存器或者高速缓存。因此,线程的操作都是以工作内存为主,它们只能访问自己的工作内存,并且在工作之前和之后,该值被同步回主内存。

说的我自己都有点晕了,用一张图来帮助我们理解吧:

线程执行的时候,将首先从主内存读值,再load到工作内存中的副本中,然后传给处理器执行,执行完毕后再给工作内存中的副本赋值,随后工作内存再把值传回给主存,主存中的值才更新。

使用工作内存和主存,虽然加快了速度,但也带来了一些问题。例如:

i = i + 1;

假设 i 的初始值为 0 ,当只有一个线程执行它的时候,结果肯定是 1 ,那么当两个线程执行时,得到的结果会是 2 吗?不一定。可能会存在这种情况:

线程1: load i from 主存    // i = 0
i + 1 // i = 1
线程2: load i from主存 // 因为线程1还没将i的值写回主存,所以i还是0
i + 1 //i = 1
线程1: save i to 主存
线程2: save i to 主存

如果两个线程遵循上面的执行过程,那么 i 的最终值竟然是 1 。如果最后的写回生效的慢,你再读取 i 的值,都可能会是 0 ,这就是缓存不一致的问题。

接下来就要提到您刚才所问的问题了,JMM主要围绕在并发过程中如何处理并发原子性、可见性和有序性这三个特征来建立的,通过解决这三个问题,就可以解决缓存不一致的问题。而volatile跟可见性和有序性都有关。

3. 那你具体说说这三个特性呢?

1 . 原子性(Atomicity):

在Java中,对基本数据类型的读取和赋值操作是原子性操作,所谓原子性操作就是指这些操作是不可中断的,要做一定做完,要么就没有执行。 例如:

i = 2;
j = i;
i++;
i = i + 1;

以上四个操作, i = 2 是一个读取操作,肯定是原子性操作, j = i 你觉得是原子性操作,但事实上,可以分为两个步骤,一个是读取 i 的值,然后再把值赋给 j ,这已经是两步操作了,不能称为原子操作,i++ 和 i = i + 1 是等效的,读的值,+ 1,然后写回主存,这是三个步骤的操作了。在上面的例子中,最后一个值可能在各种情况下,因为它不会满足原子性。

在本例中,只有一个简单的读取,赋值是一个原子操作,并且只能被分配给一个数字,使用变量来读取变量的值的操作。一个例外是,在虚拟机规范中允许64位数据类型(long和double),它被划分为两个32位操作,但是JDK的最新实现实现了原子操作。

JMM只实现基本的原子性,比如上面的i++操作,它必须依赖于同步和锁定,以确保整个代码块的原子性。在释放锁之前,线程必须将I的值返回到主内存。

2 . 可见性(Visibility):

说到可见性,Java使用volatile来提供可见性。当一个变量被volatile修改时,它的变化会立即被刷新到主存,当其他线程需要读取变量时,它会读取内存中的新值。普通变量不能保证。

事实上,同步和锁定也可以保证可见性。在释放锁之前,线程将把共享变量值刷回主内存,但是同步和锁更昂贵。

3 . 有序性(Ordering)

JMM允许编译器和处理器重新排序指令,但是指定了as-if-串行语义,也就是说,无论重新排序,程序的执行结果都不能更改。例如:

double pi = 3.14;    //A
double r = 1; //B
double s= pi * r * r;//C

上面的语句中,可以按照C - > B - >,结果是3.14,但它也可以按照的顺序B - > - > C,因为A和B是两个单独的语句,并依赖,B,C和A和B可以重新排序,但C不能行前面的A和B。JMM确保重新排序不会影响单线程的执行,但容易出现多线程问题。例如,这样的代码:

int a = 0;
bool flag = false; public void write() {
a = 2; //1
flag = true; //2
} public void multiply() {
if (flag) { //3
int ret = a * a;//4
} }

如果有两个线程执行上面的代码段,线程1首先执行写,然后再乘以线程2。最后,ret的值必须是4?不一定:

如图1和2所示,在写方法中进行重新排序,线程1对第一个赋值为true,然后执行到线程2,ret直接计算结果,然后再执行线程1,这一次的CaiFu值为2,显然是较晚的步骤。

此时要标记加上volatile关键字,重新排序,可以确保程序的“顺序”,也可以基于重量级的同步和锁定来确保,他们可以确保在代码执行的区域内一次性完成。

此外,JMM有一些内在的规律性,也就是说,没有任何方法可以保证有序,这通常称为发生在原则之前。<< jsr-133: Java内存模型和线程规范>>定义了以下事件:

  • 程序顺序规则: 一个线程中的每个操作,happens-before于该线程中的任意后续操作
  • 监视器锁规则:对一个线程的解锁,happens-before于随后对这个线程的加锁
  • volatile变量规则: 对一个volatile域的写,happens-before于后续对这个volatile域的读
  • 传递性:如果A happens-before B ,且 B happens-before C, 那么 A happens-before C
  • start()规则: 如果线程A执行操作ThreadB_start()(启动线程B) , 那么A线程的ThreadB_start()happens-before 于B中的任意操作
  • join()原则: 如果A执行ThreadB.join()并且成功返回,那么线程B中的任意操作happens-before于线程A从ThreadB.join()操作成功返回。
  • interrupt()原则: 对线程interrupt()方法的调用先行发生于被中断线程代码检测到中断事件的发生,可以通过Thread.interrupted()方法检测是否有中断发生
  • finalize()原则:一个对象的初始化完成先行发生于它的finalize()方法的开始

第1条程序顺序规则在一个线程中,所有的操作都是有序的,但实际上只要JMM的执行结果允许重新排序,这也是发生的重点——单线程执行结果是正确的,但是也不能保证多线程。

规则2,规则监视器的规则,非常好理解。在锁被添加之前,锁已经被释放,然后它才能继续被锁定。

第三条规则适用于讨论的不稳定性。如果一个行程序编写一个变量,另一个线程读取它,那么在操作之前必须读取写入操作。

第四个规则正在发生。

接下来的几行不会重复。

4. volatile关键字如何满足并发编程的三大特性的?

重新引入volatile变量规则是很重要的:对于一个不稳定域的写,出现之前,然后是对这个volatile字段的读取。本文进行再次说,如果一个变量声明事实上是不稳定,所以当我读了变量,最新的价值总是可以阅读它,这个最新的值意味着无论什么其他线程写操作,该变量将立即更新到主内存,我也可以从主内存读取只写值。也就是说,volatile关键字保证了可视性和有序度。

继续上面的代码示例:

int a = 0;
bool flag = false; public void write() {
a = 2; //1
flag = true; //2
} public void multiply() {
if (flag) { //3
int ret = a * a;//4
} }

当编写一个volatile变量时,JMM在本地内存中刷新与主内存对应的本地内存中的共享变量。

当您读取一个volatile变量时,JMM将使线程对应的本地内存失效,然后线程将从主内存读取共享变量。

Java面试官最常问的volatile关键字的更多相关文章

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

    在Java的面试当中,面试官最爱问的就是volatile关键字相关的问题.经过多次面试之后,你是否思考过,为什么他们那么爱问volatile关键字相关的问题?而对于你,如果作为面试官,是否也会考虑采用 ...

  2. 大厂面试官最常问的@Configuration+@Bean(JDKConfig编程方式)

    大厂面试官最常问的@Configuration+@Bean(JDKConfig编程方式)   现在大部分的Spring项目都采用了基于注解的配置,采用了@Configuration 替换标签的做法.一 ...

  3. java面试官最爱问的垃圾回收机制,这位阿里P7大佬分析的属实到位

    前言 JVM 内存模型一共包括三个部分: 堆 ( Java代码可及的 Java堆 和 JVM自身使用的方法区). 栈 ( 服务Java方法的虚拟机栈 和 服务Native方法的本地方法栈 ) 保证程序 ...

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

    前言 在任何Java面试当中多线程和并发方面的问题都是必不可少的一部分.如果你想获得更多职位,那么你应该准备很多关于多线程的问题. 他们会问面试者很多令人混淆的Java线程问题.面试官只是想确信面试者 ...

  5. 基础面试,为什么面试官总喜欢问String?

    关于 Java String,这是面试的基础,但是还有很多童鞋不能说清楚,所以本文将简单而又透彻的说明一下那个让你迷惑的 String 在 Java 中,我们有两种方式创建一个字符串 String x ...

  6. java面试官如何面试别人

                                                                                      java面试官如何面试别人(一) j ...

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

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

  8. 【面试普通人VS高手系列】volatile关键字有什么用?它的实现原理是什么?

    一个工作了6年的Java程序员,在阿里二面,被问到"volatile"关键字. 然后,就没有然后了- 同样,另外一个去美团面试的工作4年的小伙伴,也被"volatile关 ...

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

    在公司当技术面试官几年间,从应届生到工作十几年的应聘者都遇到过.先表达一下我自己对面试的观点: 1.笔试.面试去评价一个人肯定是不够准确的,了解一个人最准确的方式就是“路遥知马力,日久见人心”.通过一 ...

随机推荐

  1. 【翻译】针对多种设备定制Ext JS 5应用程序

    原文:Tailoring Your Ext JS 5 Application for a Multi-Device World 概述 鉴于当今设备和表单因素的扩散,要针对所有这些可能性来优化应用程序已 ...

  2. OC利用ijkplayer框架按照步骤集成实现电视直播

    一. 下载ijkplayer ijkplayer下载地址:https://github.com/Bilibili/ijkplayer 下载完成后解压, 解压后文件夹内部目录如下图: ijkplayer ...

  3. Python学习笔记 - 高阶函数

    高阶函数英文叫Higher-order function.什么是高阶函数?我们以实际代码为例子,一步一步深入概念. 变量可以指向函数 以Python内置的求绝对值的函数abs()为例,调用该函数用以下 ...

  4. UML之结尾篇

    作为十期的孩子,我们已经开发过两个系统,学生管理系统和机房收费系统,也接触了软工,编写了一系列文档,不知道小朋友有没有这种感觉,开发一个系统软件和编写一个程序是不一样的,他们之间的差别,用一个比喻来说 ...

  5. JNI动态注册native方法及JNI数据使用

    前言 或许你知道了jni的简单调用,其实不算什么百度谷歌一大把,虽然这些jni绝大多数情况下都不会让我们安卓工程师来弄,毕竟还是有点难,但是我们还是得打破砂锅知道为什么这样干吧,至少也让我们知道调用流 ...

  6. BDA大数据处理流程

    可以看出,数据处理用云,可以高效完成.而分析部分应该利用传统的bi工具.

  7. 《java入门第一季》之面向对象(方法重写问题)

    方法重载的引入:根据一个案例: /* 继承中成员方法的关系: A:子类中的方法和父类中的方法声明不一样,这个太简单. B:子类中的方法和父类中的方法声明一样,这个该怎么玩呢? 通过子类对象调用方法: ...

  8. 测试access函数

    测试程序: 测试结果: chown root access.out 将用户ID改为root chmod u+s access.out 打开 set-user-ID位

  9. 运行Myeclipse时,如何删除IVM窗口

    windows------>preference------>run/debug------->lauching--------->percpectives,改成never,n ...

  10. Android 4.1.2系统添加重启功能

    对于Android的的手机或者平板长期使用,感觉会出现慢的情况,所以偶尔还是需要重启一下,而长按电源键弹出的菜单又没有重启选项,所以特在此记录自己添加这个功能的过程. 首先关机的那个弹出菜单是在fra ...