Java内存模型

#空杯心态,一切归零#

一:什么是java内存模型

我们通过一张图片来直观的感受下内存模型的抽象概念。

在java内存模型中,本地内存(在java里面其实就是栈空间)被线程私有,主内存是所有线程共享的。对于主内存中的共享变量,每个线程会获取一个共享变量的副本,也就是在本地内存中缓存了一份数据,在线程内部都是对副本进行操作。只有在需要写回到主存时,才会交由JMM来进行控制。如果进行JMM控制,可能在竞态条件下产生线程安全问题。

再通过一张图来看下CPU多级缓存和内存的全貌。

从图中可以看出,L1和L2以及寄存器是CPU私有,L3和主存是多个CPU共享。

补充一点,CPU读取数据时的流程说明:

(1)先读取寄存器的值,如果存在则直接读取

(2)再读取L1,如果存在则先把cache行锁住,把数据读取出来,然后解锁

(3)如果L1没有则读取L2,如果存在则先将L2中的cache行加锁,然后将数据拷贝到L1,再执行读L1的过程,最后解锁

(4)如果L2没有则读取L3,同上先加锁,再往上层依次拷贝、加锁,读取到之后依次解锁

(5)如果L3也没有数据则通知内存控制器占用总线带宽,通知内存加锁,发起内存读请求,等待回应,回应数据保存到L3(如果没有就到L2),再从L3/2到L1,再从L1到CPU,之后解除总线锁定。

回到正题,结合上面的两张图,是否发现结构有些相似。工作内存对应CPU缓存,内存对应L3和RAM,都存在私有和共享。也都采用同样的思想,通过使用缓存提升程序的性能。但是任何事情都有两面性,提升的性能的同时,由此也就衍生出了缓存一致性的问题。

二:缓存一致性是如何解决的

这里主要引出volatile关键字。它的本质是告诉JVM,禁用缓存。

在CPU层面 ,最常用到的缓存一致性协议是MESI协议。主要原理是 CPU 通过总线嗅探机制(监听)可以感知数据的变化从而将自己的缓存里的数据失效,缓存行中具体的几种状态如下:

之前是锁总线,其它CPU对内存的读写请求都会被阻塞,直到锁释放,不过实际后来的处理器都采用锁缓存替代锁总线,因为锁总线的开销比较大,锁总线期间其他CPU没法访问内存。

在JVM层面,JMM通过happen-before规则和内置的八种指令来实现。

happen-before规则:本质上是可见性的问题。就是说一个线程的对共享资源的操作对另一个线程是可见的,另一个线程需要等前一个线程操作结束后,才能执行相关操作。

1、程序的顺序性规则

这条规则是指:在一个线程中,指令按顺序执行,前面的操作 Happens-Before 于后续的任意操作。

2、volatile变量规则

这条规则是指:对一个volatile变量的写操作, Happens-Before 于后续对这个变量的读操作。

3、传递性规则

这条规则是指如果A Happens-Before B,B Happens-Before C,那么A也将Happens-Before C。

4、管程中的锁的规则

这条规则是指:对一个锁的解锁 Happens-Before 于后续对这个锁的加锁操作,即,先解锁才能后加锁。

5、线程的start规则

这条规则是指:如果在线程A中启动线程B,那么该start()操作 Happens-Before 于线程B中的任意操作。

6、线程的join规则

其中的volatile变量规则,就是告诉JVM,缓存失效了,需要在使用时,从主存中获取最新的值。

另外,稍微补充下JMM提供的8条内置指令

从主存同步数据到工作内存时,需要借助read和load指令;从工作内存写回主内存时,需要用到store和write指令。其他线程要往主内存写数据时,会对缓存进行加锁,直至锁释放后才能执行相关操作。

volatile有两层含义:除了禁用缓存,还有禁止指令重排。本质是通过内存屏障来实现,为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。一个读的操作为load,写的操作为store。此处就不展开详细描述了。

三:如何解决一致性、可见性和原子性问题

synchronized和lock:在程序层面进行加锁,解决一致性问题和原子性问题。

volatile:禁用缓存,解决可见性问题。

四:对应用程序按需禁用缓存和编译优化的小建议

其实总的说来,JMM是在安全性和性能上做折中取舍,让程序中可以自己配置一些方式来实现按需禁用的目的。

final:对程序中的一些常量,使用final修饰,告诉JVM,这是一个不可变类型,不用优化。

volatile:对前后有依赖关系的逻辑,使用volatile修饰,告诉JVM,此处不用重排序。

五:写在最后

作为一个老后端程序员,第一次在平台上写技术类的文章,心中有些忐忑,也很惶恐。之前的底子也不怎么扎实,对一些技术也都掌握得不怎么成体系,很多时候,都会陷入一个误区。以为平时看到的文章或者听过的视频,自己都掌握了。但是真需要好好表达出来时候,却是又有些为难。以我自己为例,今日的这篇小文章,差不多耗费了整个下午的时光,删了写,写了删,遇到模棱两可的,再去翻阅资料。知易行难,还好,一切都有个开端,希望通过持续通过这种方式,记录自己对技术的感悟,温故而知新吧。

此处如果有小伙伴有补充的话,也欢迎在评论区留言。欢迎大家指摘,一起共勉。

相关推荐
怕什么真理无穷21 小时前
C++面试4-线程同步
java·c++·面试
拉不动的猪1 天前
# 关于初学者对于JS异步编程十大误区
前端·javascript·面试
熊猫钓鱼>_>1 天前
Java面向对象核心面试技术考点深度解析
java·开发语言·面试·面向对象··class·oop
进击的野人1 天前
CSS选择器与层叠机制
css·面试
T___T1 天前
全方位解释 JavaScript 执行机制(从底层到实战)
前端·面试
9号达人1 天前
普通公司对账系统的现实困境与解决方案
java·后端·面试
勤劳打代码1 天前
条分缕析 —— 通过 Demo 深入浅出 Provider 原理
flutter·面试·dart
努力学算法的蒟蒻1 天前
day10(11.7)——leetcode面试经典150
面试
进击的野人1 天前
JavaScript 中的数组映射方法与面向对象特性深度解析
javascript·面试
南山安1 天前
以腾讯面试题深度剖析JavaScript:从数组map方法到面向对象本质
javascript·面试