面试官:我记得上一次曾经问过了为什么要有 Java 内存模型
面试官:我记得你的最终答案是:Java 为了屏蔽硬件和操作系统拜访内存的各种差别,提出了「Java 内存模型」的标准,保障了 Java 程序在各种平台下对内存的拜访都能失去统一成果
候选者:嗯,对的
面试官 : 要不,你明天再来讲讲 Java 内存模型这里边的内容呗?
候选者:嗯,在讲之前还是得强调下:Java 内存模型它是一种「标准」,Java 虚构机会实现这个标准。
候选者:Java 内存模型次要的内容,我集体感觉有以下几块吧
候选者:1. Java 内存模型的形象构造
候选者:2. happen-before 规定
候选者:3. 对 volatile 内存语义的探讨(这块我前面再好好解释)
面试官 : 那要不你就从第一点开始呗?先聊下 Java 内存模型的形象构造?
候选者:嗯。Java 内存模型定义了:Java 线程对内存数据进行交互的标准。
候选者:线程之间的「共享变量」存储在「主内存」中,每个线程都有本人公有的「本地内存」,「本地内存」存储了该线程以读 / 写共享变量的正本。
候选者:本地内存是 Java 内存模型的抽象概念,并不是实在存在的。
候选者:顺便画个图吧,看完图就懂了。
候选者:Java 内存模型规定了:线程对变量的所有操作都必须在「本地内存」进行,「不能间接读写主内存」的变量
候选者:Java 内存模型定义了 8 种操作来实现「变量如何从主内存到本地内存,以及变量如何从本地内存到主内存」
候选者:别离是 read/load/use/assign/store/write/lock/unlock 操作
候选者:看着 8 个操作很多,对变量的一次读写就涵盖了这些操作了,我再画个图给你讲讲
候选者:懂了吧?无非就是读写用到了各个操作(:
面试官 : 懂了,很简略,接下来说什么是 happen-before 吧?
候选者:嗯,好的(:
候选者:按我的了解下,happen-before 实际上也是一套「规定」。Java 内存模型定义了这套规定,目标是为了论述「操作之间」的内存「可见性」
候选者:从上次讲述「指令重排」就提到了,在 CPU 和编译器层面上都有指令重排的问题。
候选者:指令重排尽管是能进步运行的效率,但在并发编程中,咱们在兼顾「效率」的前提下,还心愿「程序后果」能由咱们掌控的。
候选者:说白了就是:在某些重要的场景下,这一组操作都不能进行重排序,「后面一个操作的后果对后续操作必须是可见的」。
面试官:嗯…
候选者:于是,Java 内存模型就提出了 happen-before 这套规定,规定总共有 8 条
候选者:比方传递性、volatile 变量规定、程序程序规定、监视器锁的规定…(具体看规定的含意就好了,这块不难)
候选者:只有记住,有了 happen-before 这些规定。咱们写的代码只有在这些规定下,前一个操作的后果对后续操作是可见的,是不会产生重排序的。
面试官:我明确你的意思了
面试官 : 那最初说下 volatile?
候选者:嗯,volatile 是 Java 的一个关键字
候选者:为什么讲 Java 内存模型往往就会讲到 volatile 这个关键字呢,我感觉次要是它的个性:可见性和有序性(禁止重排序)
候选者:Java 内存模型这个标准,很大水平下就是为了解决可见性和有序性的问题。
面试官 : 那你来讲讲它的原理吧,volatile 这个关键字是怎么做到可见性和有序性的
候选者:Java 内存模型为了实现 volatile 有序性和可见性,定义了 4 种内存屏障的「标准」,别离是 LoadLoad/LoadStore/StoreLoad/StoreStore
候选者:回到 volatile 上,说白了,就是在 volatile「前后」加上「内存屏障」,使得编译器和 CPU 无奈进行重排序,以致有序,并且写 volatile 变量对其余线程可见。
候选者:Java 内存模型定义了标准,那 Java 虚拟机就得实现啊,是不是?
面试官:嗯…
候选者:之前看过 Hotspot 虚拟机的实现,在「汇编」层面上理论是通过 Lock 前缀指令来实现的,而不是各种 fence 指令(次要起因就是简便。因为大部分平台都反对 lock 指令,而 fence 指令是 x86 平台的)。
候选者:lock 指令能保障:禁止 CPU 和编译器的重排序(保障了有序性)、保障 CPU 写外围的指令能够立刻失效且其余外围的缓存数据生效(保障了可见性)。
面试官 : 那你提到这了,我想问问 volatile 和 MESI 协定是啥关系?
候选者:它们没有间接的关联。
候选者:Java 内存模型关注的是编程语言层面上,它是高维度的形象。MESI 是 CPU 缓存一致性协定,不同的 CPU 架构都不一样,可能有的 CPU 压根就没用 MESI 协定…
候选者:只不过 MESI 名声大,大家就都拿他来举例子了。而 MESI 可能只是在「特定的场景下」为实现 volatile 的可见性 / 有序性而应用到的一部分罢了(:
面试官:嗯…
候选者:为了让 Java 程序员屏蔽下面这些底层常识,疾速地入门应用 volatile 变量
候选者:Java 内存模型的 happen-before 规定中就有对 volatile 变量规定的定义
候选者:这条规定的内容其实就是:对一个 volatile 变量的写操作绝对于后续对这个 volatile 变量的读操作可见
候选者:它通过 happen-before 规定来规定:只有变量申明了 volatile 关键字,写后再读,读必须可见写的值。(可见性、有序性)
面试官:嗯…理解了
本文总结:
- 为什么存在 Java 内存模型:Java 为了屏蔽硬件和操作系统拜访内存的各种差别,提出了「Java 内存模型」的标准,保障了 Java 程序在各种平台下对内存的拜访都能失去统一成果
- Java 内存模型形象构造:线程之间的「共享变量」存储在「主内存」中,每个线程都有本人公有的「本地内存」,「本地内存」存储了该线程以读 / 写共享变量的正本。线程对变量的所有操作都必须在「本地内存」进行,而「不能间接读写主内存」的变量
- happen-before 规定:Java 内存模型规定在某些场景下(一共 8 条),后面一个操作的后果对后续操作必须是可见的。这 8 条规定成为 happen-before 规定
- volatile:volatile 是 Java 的关键字,润饰的变量是可见性且有序的(不会被重排序)。可见性由 happen-before 规定实现,有序性由 Java 内存模型定义的「内存屏障」实现,理论 HotSpot 虚拟机实现 Java 内存模型标准,汇编底层通过 Lock 指令来实现。
欢送关注我的微信公众号【Java3y】来聊聊 Java 面试,对线面试官系列继续更新中!
【对线面试官 - 挪动端】系列 一周两篇继续更新中!
【对线面试官 - 电脑端】系列 一周两篇继续更新中!
原创不易!!求三连!!