•  
【对线面试官】为什么需要Java内存模型?_面试1【对线面试官】为什么需要Java内存模型?_面试_02【对线面试官】为什么需要Java内存模型?_面试_03【对线面试官】为什么需要Java内存模型?_面试_04【对线面试官】为什么需要Java内存模型?_面试_05【对线面试官】为什么需要Java内存模型?_面试_06【对线面试官】为什么需要Java内存模型?_面试_07【对线面试官】为什么需要Java内存模型?_面试_08【对线面试官】为什么需要Java内存模型?_面试_09【对线面试官】为什么需要Java内存模型?_面试_10【对线面试官】为什么需要Java内存模型?_面试_11【对线面试官】为什么需要Java内存模型?_面试_12【对线面试官】为什么需要Java内存模型?_面试_13【对线面试官】为什么需要Java内存模型?_面试_14【对线面试官】为什么需要Java内存模型?_面试_15【对线面试官】为什么需要Java内存模型?_面试_16【对线面试官】为什么需要Java内存模型?_面试_17【对线面试官】为什么需要Java内存模型?_面试_18

今天的内容有点多,我再稍微总结下吧:

  • 并发问题产生的三大根源是「可见性」「有序性」「原子性」
  • 可见性: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)在屏障指令之前的操作均已被处理,进而达到 读写 在CPU层面上是可见和有序的。
  • 不同的CPU实现的架构不一样,Java为了屏蔽硬件和操作系统访问内存的各种差异,提出了「Java内存模型」的规范,保证了Java程序在各种平台下对内存的访问都能得到一致效果。

文章以纯面试的角度去讲解,所以有很多的细节是未铺垫的。

【对线面试官】为什么需要Java内存模型?_面试_19