要想深入了解Java并发编程,就要先理解好 Java内存模型 ,而要理解Java内存模型又不得不从硬件、计算机内存模型说起,本文从计算机内存模型产生的原因、解决的问题谈起,然后再对Java模型进行介绍,最后对计算机内存模型和Java内存模型进行总结,希望大家看完本文之后有所收获!
大家都知道,计算机在执行程序时,每条指令都是在CPU中执行的,而执行的时候,又免不了要和数据打交道,而计算机上面的临时数据,是储存在 主存 中的。
计算机内存包括 高速缓存 和 主存 。
我们知道CPU执行指令的速度比从主存读取数据和向主存写入数据快很多,所以为了高效利用CPU,CPU增加了**高速缓存(cache)**来匹配CPU的执行速度,最终程序的执行过程如下
上面的执行过程在单线程情况下并没有问题,但是在多线程情况下就会出现问题, 因为CPU如果含有多个核心,则每个核心都有自己独占高速缓存 ,如果出现多个线程同时执行同一个操作,那么结果是无法预知。例如 2
个线程同时执行 i++
,假设i的初始值是 0
,那么我们希望 2
个线程执行完成之后 i
的值变为 2
,但是事实会是这样吗?
可能出现的情况有:
i=0
从主存中读取到线程1的高速缓存中,然后CPU完成运算,再将 i=1
写入到主存中,然后线程2开始从主存中读取 i=1
到线程2的高速缓存中,然后CPU完成运算,再将 i=2
写入到主存中,那么 i=2
即为我们想要的结果。 i=0
从主存中读取到线程1的高速缓存中的同时线程2也从主存中读取 i=0
到线程2的高速缓存中,然后线程1和线程2完成运算后,也都将 i=1
写入到主存中,那么结果 i=1
,结果就不是我们想要的了。出现这个情况,我们称为 缓存不一致问题 。 那么如何解决CPU出现的 缓存不一致问题 呢?通常使用的解决方法有2种:
1
种方法虽然也达到了目的,但是在总线被锁住的期间,其他的CPU也无法访问主存,
效率很低 ,所以就出现了缓存一致性协议即第
2
种方法,其中最出名的就是
Intel
的MESI协议,MESI协议保证每个CPU高速缓存中的变量都是一致的。它的核心思想是,当CPU写数据时候,如果发现操作的变量是共享变量(即其他CPU上也存在该变量),就会发出信号通知
其他CPU 将它高速缓存中缓存这个变量的缓存行置为
无效状态 ,因此当其他CPU需要读取这个变量时,发现自己高速缓存中缓存该变量的缓存行为无效状态,那么它就会从主存中
重新读取
。
在多线程场景下,CPU除了会出现缓存一致性问题,还会出现因为 处理器重排序 即 处理器(CPU)为了提高效率可能会对输入的代码进行乱序执行 ,而造成多线程的情况下出现问题。 例如:
//线程1: context = loadContext(); //语句1 inited = true; //语句2 //线程2: while(!inited ){ sleep() } doSomethingwithconfig(context); 复制代码
线程1由于处理器重排序,先执行性了语句2,那么此时线程2会认为 context
已经初始化完成,那么跳出循环,去执行 doSomethingwithconfig(context)
方法,实际上此时context并未初始化(即线程1的语句1还未执行),而导致程序出错。
上面提到的 缓存一致性问题 、 处理器重排序问题 都是在多线程情况下CPU可能出现的问题,那我们应该怎么处理这些问题?实际上这些问题并不需要我们考虑,这些问题CPU都会处理好,而CPU处理这些问题的时候是按照特定的 操作规范 ,对特定的主存进行访问或告诉CPU高速缓存怎么访问主存,保证了多线程场景下的 原子性、可见性、有序性 ,这个操作规范就称为 计算机内存模型 。
可见性即当一个变量修改后,这个变量会马上更新到主存中,其他线程会收到通知这个变量修改过了,使用这个变量的时候重新去主存获取
从前面的介绍了解到计算机内存模型是一种解决多线程场景下的一个主存操作规范,既然是规范,那么不同的编程语言都可以遵循这种操作规范,在多线程场景下访问主存保证原子性、可见性、有序性。
Java内存模型(Java Memory Model,JMM) 即是Java语言对这个操作规范的遵循, JMM
规定了所有的变量都存储在 主存 中,每个线程都有自己的 工作区 ,线程将使用到的变量从主存中 复制 一份到自己的工作区,线程对变量的 所有操作 (读取、赋值等)都必须在工作区,不同的线程也无法直接访问对方工作区,线程之间的消息传递都需要通过主存来完成。 可以把这里主存类比成计算机内存模型中的主存,工作区类比成计算机内存模型中的高速缓存 。
而我们知道 JMM
其实是工作主存中的,Java内存模型中的工作区也是主存中的一部分,所以可以这样说Java内存模型解决的是 内存一致性问题(主存和主存) 而计算机内存模型解决的是 缓存一致性问题(CPU高速缓存和主存) ,这两个模型类似,但是作用域不一样,Java内存模型保证的是主存和主存之间的原子性、可见性、有序性,而计算机内存模型保证的是CPU高速缓存和主存之间的原子性、可见性、有序性。
本文很多观点都是按照笔者自己的理解然后总结出来的,若有偏颇,欢迎指正!