JAVA内存模型
今天我们讲一下java内存模型(JMM),JMM的问题在面试中经常被问到,我们今天就讲一下这道题.
在正式Java内存模型之前,先讲一下机器硬件CPU.
计算机的并发问题和虚拟机的情况有不少相似之处,我们知道所有的运算操作都是由CPU完成的.CPU指令就牵扯到了数据的操作,难免就会和主内存打交道,虽然CPU发展的性能越来越好,但还是受限于内存的访问速度,通常这种差异是千差万别的,极端情况甚至上万倍.
基于上面原因,便有了加入缓存(CPU Cache)的机制,CPU Cache 充当CPU 和主内存的缓冲,就有了下面的操作
下面就是CPU的架构图
虽然这样解决速度的问题,但是也带来了另外一个问题,缓存一致性,举个例子如i++。
i++在单线程不会有问题,但是多线程下就会有问题,假设i的初始值是0,两个线程本地都把i=0缓存到的CPU Cache中,然后都对i进行加一操作后,然后把他们放到主内存,就有可能存储的i还是1,这就是典型的缓存不一致.
这问题也有解决的办法,有两种:
第一种是早期的解决办法, 他的原理就是一种悲观的实现,有多个CPU要和主内存交互的之前,都要和总线(数据总线,控制总线,地址总线)进行通信,总线只会允许一个CPU访问变量的主内存,这种效率太低,第二种,就是缓存一致性方式解决一致性问题,看一下的图,如下
缓存一致性最为出名的是Intel的MESI协议,保证了缓存使用中共享变量副本都是一致的,他的原理是:
理解了上面的内存,那对Java内存模型学习就很容易了,但是他们不是一个东西,我们先看一下Java内存模型是啥样子
java的内存模型决定了一个线程对共享变量的写入何时对其他线程可见,Java内存模型定义线程和主内存之间的抽象关系,具体如下
我们再来看一个例子
如图,本地内存A,和本地内存B有主内存中共享变量的X的副本,初始时,这3个内存中的x都是0,线程A执行后,把更新的值,临时放到自己的本地内存A中,当线程A和线程B通讯时候,线程A首先会把自己本地内存修改的值,放到主内存中,此时,B的本地内存已经失效,线程B到主内存拿到的值就是线程A更新后的值,线程B的本地内存就会变成1。这一点和CPU和CPU Cache 关系非常相似。
切记,他们并不完全一样,比如计算机物理内存不会存在栈内存,堆内存的划分,无论是堆内存还是虚拟机栈内存都会对应到物理的主内存,当然也有一部分的数据有可能存入CPU Cache寄存器中。如下图
JMM是一种规范,目的是解决由于多线程通过共享内存进行通信时,存在的本地内存数据不一致、编译器会对代码指令重排序、处理器会对代码乱序执行等带来的问题,java中的许多关键字是使用java内存模型封装了底层给我们使用,如synchronized ,volatile,final.这里我们不展开讲解,有兴趣的可以自行学习。