知道这些面试时volatile就稳了

4次阅读

共计 1955 个字符,预计需要花费 5 分钟才能阅读完成。

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

本文以一次假想的面试过程,来深入了解下 volitile 关键字
题目有标题党的嫌疑,但是如果大家好好理解文中涉及到的两篇文章,相信你对 volatile 有更深刻的认识!

【灵魂拷问开始】

面试官:Java 并发这块了解的怎么样?说说你对 volatile 关键字的理解?
面试官:能不能详细说下什么是内存可见性,什么又是重排序呢?
面试官:volatile 怎么保证可见性的?多个线程之间的可见性,你能讲一下底层原理是怎么实现的吗?
面试官:volatile 关键字是怎么保证有序性的?
面试官:volatile 能保证可见性和有序性,但是能保证原子性吗?为什么?
面试官:了解过 JMM 内存模型吗?简单的讲讲
到这里,我的眼里已是常含泪水了。不是因为我对代码爱的深沉,而是因为我菜的真诚!

没事,不就是个破 volatile 吗?别念了,我学习还不行吗!

Q1:请你谈谈对 volatile 关键字的理解?

volatile 是 JVM 提供的轻量级的同步机制

1. 保证可见性
2. 保证有序性,禁止指令重排
3. 不保证原子性(需要借助 synchronized 或者 CAS)

小伙子,不错么????,回答对了 1 + 1,得来点 2 + 2 的难度了

Q2:什么是内存可见性,volatile 怎么保证多个线程之间的可见性的?

问题 2 和问题 3 讲到的可见性,用 JMM 来解释的话,本质上是同一个问题。至于有序性和重排序,到问题 4 再讨论。

一问到内存的可见性,volatile 相关的,直接就把 JMM 内存模型搬出来好吧。先放图,然后再表演。

以下是俺个人的回答,有不足或漏洞,欢迎大家更正指出!

所谓可见性,是指当一条线程修改了共享变量的值,新值对于其他线程来说是可以立即得知的

每个线程都有自己独立的工作区间,为了匹配 CPU 的运行速度,他们不会直接从内存中读取数据,而是将数据拷贝一份到 CPU 缓存中(即每个线程自己的工作内存),他们之间的相互交互,是通过内存来完成的。

根据 JMM 内存模型的 8 大原子操作,每个线程在 j 将数据操作完 stroe 回主存之前,会加 lock 指令来锁定内存区域的缓存(缓存行锁定),根据 MESI 缓存一致性协议,总线通过侦听器发现数据被修改,会立即让其他线程工作内存中不一致的副本立即失效。

等到当前线程将更改后的数据 write 回主存后,立即执行 unlock 指令。

此时,其他线程再重新读取更新后的数据,再拷贝到自己的工作内存。总线侦听机制会在总线上检测线程的数据,发现有线程做了更改时准备 store 回主内存时,它就会立刻将其他线程工作内存中的副本置位无效,然后从新到主存获取更新后的值。

除了使用 volatile 关键字来保证内存可见性之外,使用 synchronized 或 Lock 锁也能保证变量的内存可见性。只是相比而言使用 volatile 关键字开销更小,是轻量级的锁。

这就是内存可见性的原理。

Q3:volatile 关键字是怎么保证有序性的?

使用 volatile 关键字修饰共享变量便可以禁止指令重排序。若用 volatile 修饰共享变量,在 JVM 底层 volatile 是采用“内存屏障”来实现禁止特定类型的处理器重排序。加入 volatile 关键字时,会多出一个 lock 前缀指令,lock 前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供 3 个功能:

1. 它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;

2. 它会强制将对缓存的修改操作立即写入主存;

3. 如果是写操作,它会导致其他 CPU 中对应的缓存行无效。

JMM 具备一些先天的有序性,通过 Happens-Before 原则就可以保证的一定的有序性。

Q4:volatile 能保证可见性和有序性,但是能保证原子性吗?为什么?

volatile 关键字不能保证原子性。

a. 当写一个 volatile 变量时,JMM 会把该线程本地内存中的变量强制刷新到主内存中去;

b. 这个写会操作会导致其他线程中的缓存无效。

对于类似 i ++ 这样的复合操作,要想保证原子性,只能借助于 synchronized、Lock 以及并发包下的 AtomicInteger 的原子操作类。AtomicInteger 对基本数据类型的 自增(加 1 操作),自减(减 1 操作)、以及加法操作(加一个数),减法操作(减一个数)进行了封装,保证这些操作是原子性操作。

Q5:了解过 JMM 内存模型吗?简单的讲讲

看着这张图,直接就巴拉巴拉一顿操作!

正文完
 0