深入浅出Java内存模型
面试官:我记得上一次已经问过了为什么要有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内存模型】候选者:说白了就是:在某些重要的场景下,这一组操作都不能进行重排序,「前面一个操作的结果对后续操作必须是可见的」。
文章图片
面试官:嗯…
候选者:于是,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面试,对线面试官系列持续更新中!
【对线面试官-移动端】系列 一周两篇持续更新中!
【对线面试官-电脑端】系列 一周两篇持续更新中!
原创不易!!求三连!!
推荐阅读
- JAVA(抽象类与接口的区别&重载与重写&内存泄漏)
- 事件代理
- Java|Java OpenCV图像处理之SIFT角点检测详解
- java中如何实现重建二叉树
- 数组常用方法一
- 【Hadoop踩雷】Mac下安装Hadoop3以及Java版本问题
- Java|Java基础——数组
- RxJava|RxJava 在Android项目中的使用(一)
- java之static、static|java之static、static final、final的区别与应用
- Java基础-高级特性-枚举实现状态机