网站跳出率多少算正常,传媒公司网站建设思路,电商平台网页制作,做外贸怎样打开国外网站Java内存模型#xff08;JMM#xff09;是基于多线程的吗
这个问题按我的思路转换了下#xff0c;其实就是在问#xff1a;为什么需要Java内存模型
总结起来可以由几个角度来看待「可见性」、「有序性」和「原子性」 面试官#xff1a;今天想跟你聊聊Java内存模型#…Java内存模型JMM是基于多线程的吗
这个问题按我的思路转换了下其实就是在问为什么需要Java内存模型
总结起来可以由几个角度来看待「可见性」、「有序性」和「原子性」 面试官今天想跟你聊聊Java内存模型这块你了解过吗
候选者嗯我简单说下我的理解吧。那我就从为什么要有Java内存模型开始讲起吧
面试官开始你的表演吧。
候选者那我先说下背景吧
候选者1. 现有计算机往往是多核的每个核心下会有高速缓存。高速缓存的诞生是由于「CPU与内存(主存)的速度存在差异」L1和L2缓存一般是「每个核心独占」一份的。
候选者2. 为了让CPU提高运算效率处理器可能会对输入的代码进行「乱序执行」也就是所谓的「指令重排序」
候选者3. 一次对数值的修改操作往往是非原子性的比如i实际上在计算机执行时就会分成多个指令
候选者在永远单线程下上面所讲的均不会存在什么问题因为单线程意味着无并发。并且在单线程下编译器/runtime/处理器都必须遵守as-if-serial语义遵守as-if-serial意味着它们不会对「数据依赖关系的操作」做重排序。 候选者CPU为了效率有了高速缓存、有了指令重排序等等整块架构都变得复杂了。我们写的程序肯定也想要「充分」利用CPU的资源啊于是乎我们使用起了多线程
候选者多线程在意味着并发并发就意味着我们需要考虑线程安全问题
候选者1. 缓存数据不一致多个线程同时修改「共享变量」CPU核心下的高速缓存是「不共享」的那多个cache与内存之间的数据同步该怎么做
候选者2. CPU指令重排序在多线程下会导致代码在非预期下执行最终会导致结果存在错误的情况。 候选者针对于「缓存不一致」问题CPU也有其解决办法常被大家所认识的有两种
候选者1.使用「总线锁」某个核心在修改数据的过程中其他核心均无法修改内存中的数据。类似于独占内存的概念只要有CPU在修改那别的CPU就得等待当前CPU释放
候选者2.缓存一致性协议MESI协议其实协议有很多只是举个大家都可能见过的。MESI拆开英文是Modified 修改状态、Exclusive 独占状态、Share共享状态、Invalid无效状态
候选者缓存一致性协议我认为可以理解为「缓存锁」它针对的是「缓存行」(Cache line) 进行”加锁”所谓「缓存行」其实就是 高速缓存 存储的最小单位。 面试官嗯…
候选者MESI协议的原理大概就是当每个CPU读取共享变量之前会先识别数据的「对象状态」(是修改、还是共享、还是独占、还是无效)。
候选者如果是独占说明当前CPU将要得到的变量数据是最新的没有被其他CPU所同时读取
候选者如果是共享说明当前CPU将要得到的变量数据还是最新的有其他的CPU在同时读取但还没被修改
候选者如果是修改说明当前CPU正在修改该变量的值同时会向其他CPU发送该数据状态为invalid(无效)的通知得到其他CPU响应后其他CPU将数据状态从共享(share)变成invalid(无效)会当前CPU将高速缓存的数据写到主存并把自己的状态从modify(修改)变成exclusive(独占)
候选者如果是无效说明当前数据是被改过了需要从主存重新读取最新的数据。 候选者其实MESI协议做的就是判断「对象状态」根据「对象状态」做不同的策略。关键就在于某个CPU在对数据进行修改时需要「同步」通知其他CPU表示这个数据被我修改了你们不能用了。
候选者比较于「总线锁」MESI协议的”锁粒度”更小了性能那肯定会更高咯
面试官但据我了解CPU还有优化你还知道吗
候选者嗯还是了解那么一点点的。
候选者从前面讲到的可以发现的是当CPU修改数据时需要「同步」告诉其他的CPU等待其他CPU响应接收到invalid(无效)后它才能将高速缓存数据写到主存。
候选者同步意味着等待等待意味着什么都干不了。CPU肯定不乐意啊所以又优化了一把。
候选者优化思路就是从「同步」变成「异步」。
候选者在修改时会「同步」告诉其他CPU而现在则把最新修改的值写到「[store buffer](https://www.zhihu.com/search?qstore buffersearch_sourceEntityhybrid_search_sourceEntityhybrid_search_extra{“sourceType”%3Aanswer%2CsourceId%3A2215772844})」中并通知其他CPU记得要改状态随后CPU就直接返回干其他事了。等到收到其它CPU发过来的响应消息再将数据更新到高速缓存中。
候选者其他CPU接收到invalid(无效)通知时也会把接收到的消息放入「invalid queue」中只要写到「invalid queue」就会直接返回告诉修改数据的CPU已经将状态置为「invalid」 候选者而异步又会带来新问题那我现在CPU修改完A值写到「store buffer」了CPU就可以干其他事了。那如果该CPU又接收指令需要修改A值但上一次修改的值还在「store buffer」中呢没修改至高速缓存呢。
候选者所以CPU在读取的时候需要去「store buffer」看看存不存在存在则直接取不存在才读主存的数据。【Store Forwarding】
候选者好了解决掉第一个异步带来的问题了。相同的核心对数据进行读写由于异步很可能会导致第二次读取的还是旧值所以首先读「store buffer」。
面试官还有其他
候选者那当然啊那「异步化」会导致相同核心读写共享变量有问题那当然也会导致「不同」核心读写共享变量有问题啊
候选者CPU1修改了A值已把修改后值写到「store buffer」并通知CPU2对该值进行invalid(无效)操作而CPU2可能还没收到invalid(无效)通知就去做了其他的操作导致CPU2读到的还是旧值。
候选者即便CPU2收到了invalid(无效)通知但CPU1的值还没写到主存那CPU2再次向主存读取的时候还是旧值…
候选者变量之间很多时候是具有「相关性」(a1;b0;ba)这对于CPU又是无感知的…
候选者总体而言由于CPU对「缓存一致性协议」进行的异步优化「store buffer」「invalid queue」很可能导致后面的指令很可能查不到前面指令的执行结果各个指令的执行顺序非代码执行顺序这种现象很多时候被称作「CPU乱序执行」
候选者为了解决乱序问题也可以理解为可见性问题修改完没有及时同步到其他的CPU又引出了「内存屏障」的概念。 面试官嗯…
候选者「内存屏障」其实就是为了解决「异步优化」导致「CPU乱序执行」/「缓存不及时可见」的问题那怎么解决的呢嗯就是把「异步优化」给”禁用“掉
候选者内存屏障可以分为三种类型写屏障读屏障以及全能屏障包含了读写屏障屏障可以简单理解为在操作数据的时候往数据插入一条”特殊的指令”。只要遇到这条指令那前面的操作都得「完成」。
候选者那写屏障就可以这样理解CPU当发现写屏障的指令时会把该指令「之前」存在于「store Buffer」所有写指令刷入高速缓存。
候选者通过这种方式就可以让CPU修改的数据可以马上暴露给其他CPU达到「写操作」可见性的效果。
候选者那读屏障也是类似的CPU当发现读屏障的指令时会把该指令「之前」存在于「invalid queue」所有的指令都处理掉
候选者通过这种方式就可以确保当前CPU的缓存状态是准确的达到「读操作」一定是读取最新的效果。 候选者由于不同CPU架构的缓存体系不一样、缓存一致性协议不一样、重排序的策略不一样、所提供的内存屏障指令也有差异为了简化Java开发人员的工作。Java封装了一套规范这套规范就是「Java内存模型」
候选者再详细地说「Java内存模型」希望 屏蔽各种硬件和操作系统的访问差异保证了Java程序在各种平台下对内存的访问都能得到一致效果。目的是解决多线程存在的原子性、可见性缓存一致性以及有序性问题。 面试官那要不简单聊聊Java内存模型的规范和内容吧
候选者不了怕一聊就是一个下午下次吧
本文总结 并发问题产生的三大根源是「可见性」「有序性」「原子性」 可见性CPU架构下存在高速缓存每个核心下的L1/L2高速缓存不共享不可见 有序性主要有三方面可能导致打破 编译器优化导致重排序编译器可以在不改变单线程程序语义的情况下可以对代码语句顺序进行调整重新排序指令集并行重排序CPU原生就有可能将指令进行重排内存系统重排序CPU架构下很可能有store buffer /invalid queue 缓冲区这种「异步」很可能会导致指令重排 原子性Java的一条语句往往需要多条 CPU 指令完成(i)由于操作系统的线程切换很可能导致 i 操作未完成其他线程“中途”操作了共享变量 i 导致最终结果并非我们所期待的。 在CPU层级下为了解决「缓存一致性」问题有相关的“锁”来保证比如“总线锁”和“缓存锁”。 总线锁是锁总线对共享变量的修改在相同的时刻只允许一个CPU操作。缓存锁是锁缓存行(cache line)其中比较出名的是MESI协议对缓存行标记状态通过“同步通知”的方式来实现(缓存行)数据的可见性和有序性但“同步通知”会影响性能所以会有内存缓冲区(store buffer/invalid queue)来实现「异步」进而提高CPU的工作效率引入了内存缓冲区后又会存在「可见性」和「有序性」的问题平日大多数情况下是可以享受「异步」带来的好处的但少数情况下需要强「可见性」和「有序性」只能”禁用”缓存的优化。“禁用”缓存优化在CPU层面下有「内存屏障」读屏障/写屏障/全能屏障本质上是插入一条”屏障指令”使得缓冲区(store buffer/[invalid queue](https://www.zhihu.com/search?qinvalid queuesearch_sourceEntityhybrid_search_sourceEntityhybrid_search_extra{“sourceType”%3Aanswer%2CsourceId%3A2215772844}))在屏障指令之前的操作均已被处理进而达到 读写 在CPU层面上是可见和有序的。 不同的CPU实现的架构和优化均不一样Java为了屏蔽硬件和操作系统访问内存的各种差异提出了「Java内存模型」的规范保证了Java程序在各种平台下对内存的访问都能得到一致效果
写在最后 编程严选网www.javaedge.cn程序员的终身学习网站已上线 如果这篇【文章】有帮助到你希望可以给【JavaGPT】点个赞创作不易如果有对【后端技术】、【前端领域】感兴趣的小可爱也欢迎关注❤️❤️❤️ 【JavaGPT】❤️❤️❤️我将会给你带来巨大的【收获与惊喜】