面试官:我记得上一次已经问过了为什么要有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面试,对线面试官系列持续更新中!

【对线面试官-移动端】系列 一周两篇持续更新中!

【对线面试官-电脑端】系列 一周两篇持续更新中!

原创不易!!求三连!!

深入浅出Java内存模型的更多相关文章

  1. 【Java并发基础】Java内存模型解决有序性和可见性

    前言 解决并发编程中的可见性和有序性问题最直接的方法就是禁用CPU缓存和编译器的优化.但是,禁用这两者又会影响程序性能.于是我们要做的是按需禁用CPU缓存和编译器的优化. 如何按需禁用CPU缓存和编译 ...

  2. JVM学习(3)——总结Java内存模型

    俗话说,自己写的代码,6个月后也是别人的代码……复习!复习!复习!涉及到的知识点总结如下: 为什么学习Java的内存模式 缓存一致性问题 什么是内存模型 JMM(Java Memory Model)简 ...

  3. 浅析java内存模型--JMM(Java Memory Model)

    在并发编程中,多个线程之间采取什么机制进行通信(信息交换),什么机制进行数据的同步? 在Java语言中,采用的是共享内存模型来实现多线程之间的信息交换和数据同步的. 线程之间通过共享程序公共的状态,通 ...

  4. JMM(java内存模型)

    What is a memory model, anyway? In multiprocessorsystems, processors generally have one or more laye ...

  5. 《深入理解Java内存模型》读书总结

    概要 文章是<深入理解Java内容模型>读书笔记,该书总共包括了3部分的知识. 第1部分,基本概念 包括"并发.同步.主内存.本地内存.重排序.内存屏障.happens befo ...

  6. Java内存模型深度解析:final--转

    原文地址:http://www.codeceo.com/article/java-memory-6.html 与前面介绍的锁和Volatile相比较,对final域的读和写更像是普通的变量访问.对于f ...

  7. Java内存模型深度解析:volatile--转

    原文地址:http://www.codeceo.com/article/java-memory-4.html Volatile的特性 当我们声明共享变量为volatile后,对这个变量的读/写将会很特 ...

  8. Java内存模型深度解析:顺序一致性--转

    原文地址:http://www.codeceo.com/article/java-memory-3.html 数据竞争与顺序一致性保证 当程序未正确同步时,就会存在数据竞争.java内存模型规范对数据 ...

  9. Java内存模型深度解析:基础部分--转

    原文地址:http://www.codeceo.com/article/java-memory-1.html 并发编程模型的分类 在并发编程中,我们需要处理两个关键问题:线程之间如何通信及线程之间如何 ...

随机推荐

  1. 20210808 Hunter,Defence,Connect

    考场 乍一看都不可做 T1 算了半天样例,一直算出来 \(\frac{81}{400}\),直接丢了 T1 推了推发现是求最长连续 \(0\) 的数量,那就是线段树合并加上<玫瑰花精> T ...

  2. 自定义cs程序安装界面

    http://www.crifan.com/achieve_fixed_install_path_while_install_software_when_make_msi_installer/ 制作C ...

  3. 个人作业--体温上报APP

    第一阶段目标: 1.要求增加用户注册功能,用户注册信息包括用户ID(学号).用户名(姓名),手机号码,用户单位(班级),用户班级四项基本信息,用户第一次注册后,用户姓名不用每次输入 . 2.体温上报界 ...

  4. [CSP-J2020] 优秀的拆分

    [CSP-J2020] 优秀的拆分 难度:普及- 题目描述 一般来说,一个正整数可以拆分成若干个正整数的和. 例如,1=1,10=1+2+3+4 等.对于正整数 n 的一种特定拆分,我们称它为&quo ...

  5. python循环以及控制语句

    python流程 学习完本篇,你将会通过python完成以下题目 试利用break语句求解2-100之间的素数. (1)素数是指除了能被1和它本身整除外,不能被其它数所整除的数.判断一个自然数是否是素 ...

  6. TP6生成url

    和TP5生成url方式有区别, 在控制器里 需要加上 ->build(), 如 url('index/arc/list')->build(); 如果是多域名还需要指定域名,如不想加域名可以 ...

  7. 使用正则表达式在VS中批量移除 try-catch

    使用正则表达式在VS中批量移除 try-catch 前言 try-catch 意为捕获错误,一般在可能出错的地方使用(如调用外部函数或外部设备),以对错误进行正确的处理,并进行后续操作而不至于程序直接 ...

  8. django 使用装饰器验证用户登陆

    使用装饰器验证用户登陆,需要使用@method_decorator 首先需引用,method_decorator,并定义一个闭包 from django.utils.decorators import ...

  9. JavaScript --css样式

    1.JavaScript显示隐藏控制 隐藏:display:none; 显示:display:block; 参考链接:https://blog.csdn.net/sleepwalker_1992/ar ...

  10. AT3611-Tree MST【点分治,最小生成树】

    正题 题目链接:https://www.luogu.com.cn/problem/AT3611 题目大意 给出\(n\)个点的一棵树. 现在有一张完全图,两个点之间的边权为\(w_x+w_y+dis( ...