什么是Java内存模型?

image

前言

要想深刻了解Java并发编程,就要先理解好Java内存模型,而要理解Java内存模型又不得不从硬件、计算机内存模型提及,本文从计算机内存模型产生的缘由、解决的问题谈起,而后再对Java模型进行介绍,最后对计算机内存模型和Java内存模型进行总结,但愿你们看完本文以后有所收获!html

CPU工做过程及出现的问题

CPU执行过程

你们都知道,计算机在执行程序时,每条指令都是在CPU中执行的,而执行的时候,又免不了要和数据打交道,而计算机上面的临时数据,是储存在主存中的。java

计算机内存包括高速缓存主存编程

咱们知道CPU执行指令的速度比从主存读取数据和向主存写入数据快不少,因此为了高效利用CPU,CPU增长了**高速缓存(cache)**来匹配CPU的执行速度,最终程序的执行过程以下缓存

  1. 首先会将数据从主存中复制一份到CPU的高速缓存中
  2. 当CPU执行计算的时候就能够直接从高速缓存中读取数据和写入数据
  3. 当运算结束后,再将高速缓存的数据更新到主存中

缓存一致性问题

上面的执行过程在单线程状况下并无问题,可是在多线程状况下就会出现问题,由于CPU若是含有多个核心,则每一个核心都有本身独占高速缓存,若是出现多个线程同时执行同一个操做,那么结果是没法预知。例如2个线程同时执行i++,假设i的初始值是0,那么咱们但愿2个线程执行完成以后i的值变为2,可是事实会是这样吗?
bash

可能出现的状况有:多线程

  1. 线程1先将i=0从主存中读取到线程1的高速缓存中,而后CPU完成运算,再将i=1写入到主存中,而后线程2开始从主存中读取i=1到线程2的高速缓存中,而后CPU完成运算,再将i=2写入到主存中,那么i=2即为咱们想要的结果。
  2. 线程1将i=0从主存中读取到线程1的高速缓存中的同时线程2也从主存中读取i=0到线程2的高速缓存中,而后线程1和线程2完成运算后,也都将i=1写入到主存中,那么结果i=1,结果就不是咱们想要的了。出现这个状况,咱们称为缓存不一致问题

那么如何解决CPU出现的缓存不一致问题呢?一般使用的解决方法有2种:并发

  1. 经过给总线加锁
  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还未执行),而致使程序出错。spa

什么是计算机内存模型

上面提到的缓存一致性问题处理器重排序问题都是在多线程状况下CPU可能出现的问题,那咱们应该怎么处理这些问题?实际上这些问题并不须要咱们考虑,这些问题CPU都会处理好,而CPU处理这些问题的时候是按照特定的操做规范,对特定的主存进行访问或告诉CPU高速缓存怎么访问主存,保证了多线程场景下的原子性、可见性、有序性,这个操做规范就称为计算机内存模型.net

可见性即当一个变量修改后,这个变量会立刻更新到主存中,其余线程会收到通知这个变量修改过了,使用这个变量的时候从新去主存获取

什么是Java内存模型

从前面的介绍了解到计算机内存模型是一种解决多线程场景下的一个主存操做规范,既然是规范,那么不一样的编程语言均可以遵循这种操做规范,在多线程场景下访问主存保证原子性、可见性、有序性。
Java内存模型(Java Memory Model,JMM)便是Java语言对这个操做规范的遵循,JMM规定了全部的变量都存储在主存中,每一个线程都有本身的工做区,线程将使用到的变量从主存中复制一份到本身的工做区,线程对变量的全部操做(读取、赋值等)都必须在工做区,不一样的线程也没法直接访问对方工做区,线程之间的消息传递都须要经过主存来完成。能够把这里主存类比成计算机内存模型中的主存,工做区类比成计算机内存模型中的高速缓存

而咱们知道JMM实际上是工做主存中的,Java内存模型中的工做区也是主存中的一部分,因此能够这样说Java内存模型解决的是内存一致性问题(主存和主存)而计算机内存模型解决的是缓存一致性问题(CPU高速缓存和主存),这两个模型相似,可是做用域不同,Java内存模型保证的是主存和主存之间的原子性、可见性、有序性,而计算机内存模型保证的是CPU高速缓存和主存之间的原子性、可见性、有序性。

总结

本文不少观点都是按照笔者本身的理解而后总结出来的,如有偏颇,欢迎指正!

参考

Java并发编程:volatile关键字解析
Java内存模型
【教程】终于有人把Java内存模型说清楚了!
关于JAVA内存模型与MESI协议?
有了缓存一致性协议为何还须要多线程同步?

原文地址:https://ddnd.cn/2019/03/11/java-memory-model/

相关文章
相关标签/搜索