一、物理机对并发的处理方案
在介绍Java内存模型前,我们需要先了解一下物理机对并发的处理方案
- 由于计算机处理器和存储设备的运算速度有几个数量级的差别,处理器快速的计算完成后,总是需要等待I/O操作的完成,在等待期间处理器无法进行其他操作,因此十分浪费资源
处理器菌:( ▼-▼ )你好慢噢
I/O设备酱:〒_〒我也没有办法酱
- 所以现代计算机系统都加入了一层读写速度尽可能接近处理器运算速度的高速缓存来作为内存与处理器间的缓冲,具体的作用就是:
- 将运算需要使用到的数据复制到缓存中,让运算能够快速进行
- 当运算结束后再从缓存同步回内存中,这样处理器就无需等待缓慢的内存读写了
处理器菌:( ▼-▼ )你好慢噢
高速缓存萌妹子:我来搞定(●’◡’●)
I/O设备酱:〒_〒我也没有办法酱
- 但是这样存在一个问题:缓存一致性(Cache Coherence)
其实就是并发的问题,在多处理器系统中,每个处理器都有自己的高速缓存,而它们却又共享同一块内存,当涉及到这一块内存时,将可能导致各自的缓存数据不一致
处理器A菌:我说了算!(╯▔皿▔)╯
处理器B酱:(掀桌子)听我的!(╯-_-)╯╧╧
- 那么怎么解决这个问题呢?让这些处理器访问缓存时都遵循一些协议!这就像在计算机网络中我们所学习的各种各样的协议一样,是一种规则、约束,让处理器在读写时根据协议来进行操作,便可解决缓存一致性问题。
处理器A菌:听协议大大的(没脾气)
处理器B酱:听协议大大的(没脾气)
而内存模型,就可以理解为在特定的操作协议下,对特定的内存或高速缓存进行读写访问的过程抽象。
不同架构的物理机器可以拥有不同的内存模型,而Java虚拟机也有自己的内存模型~
二、Java内存模型(Java Memory Model, JMM)
JMM想做到的是:屏蔽各种硬件和操作系统的内存访问差异,从而实现“Write once, run everywhere”一次编写,到处运行的效果
其他语言如C/C++都是直接使用物理硬件和操作系统的内存模型,因此会由于不同平台上内存模型的差异,导致程序无法在多个平台运行正常,因此有时必须针对不同平台来编写程序。
1. 主内存与工作内存
同样的,与物理机中处理并发的方式类似,JVM的内存模型也有相应的处理并发的机制
那么它是怎么做的呢?
就是将内存分为主内存和工作内存,
此处的主内存可以类比为物理硬件中的主内存,是线程所共享的一块内存;
此处的工作内存可以类比为高速缓存,是线程自己私有的内存。
采用这种与物理机所采取的类似的方式,在JVM中也解决了“缓存一致性”问题。
JMM的主要目标是:定义程序中各个变量的访问规则,即在VM中将变量存储到内存与从内存中取出变量这样的底层细节。
JMM规定所有变量都存在主内存(Main Memory)中,同时,每条线程都有自己的工作内存(Working Memory)。
主内存可以类比为物理硬件的主内存,是线程共享的一块内存,而工作内存其实就相当于前面提到的处理器的高速缓存,是线程私有的内存空间,
对于工作内存,需要注意的是:
线程的工作内存中保存了被该线程使用到的变量的主内存的副本拷贝
线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变量
不同线程间无法直接访问对方工作内存中的变量,只能通过主内存来完成线程间变量值的传递
图源网络
2. 内存间交互操作
关于主内存与工作内存间具体的交互协议,JMM定义了8种操作来完成,这8种操作在VM实现时必须保证每一种都是原子的、不可再分的。
除了8种操作外还有一些其他的规则,这些规则限定以及对volatile的特殊规定,就可以完全确定了Java程序中哪些内存访问操作在并发下是安全的。
但这些定义太过繁琐,因此我们一般使用另一种等效判断原则-先行发生原则(happens-before),来确定一个访问在并发环境下是否安全