#空杯心态,一切归零#
一:什么是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,此处不用重排序。
五:写在最后
作为一个老后端程序员,第一次在平台上写技术类的文章,心中有些忐忑,也很惶恐。之前的底子也不怎么扎实,对一些技术也都掌握得不怎么成体系,很多时候,都会陷入一个误区。以为平时看到的文章或者听过的视频,自己都掌握了。但是真需要好好表达出来时候,却是又有些为难。以我自己为例,今日的这篇小文章,差不多耗费了整个下午的时光,删了写,写了删,遇到模棱两可的,再去翻阅资料。知易行难,还好,一切都有个开端,希望通过持续通过这种方式,记录自己对技术的感悟,温故而知新吧。
此处如果有小伙伴有补充的话,也欢迎在评论区留言。欢迎大家指摘,一起共勉。