面试Java内存模型-蚂蚁金服

327 阅读5分钟

JAVA内存模型

今天我们讲一下java内存模型(JMM),JMM的问题在面试中经常被问到,我们今天就讲一下这道题. ​在正式Java内存模型之前,先讲一下机器硬件CPU. 计算机的并发问题和虚拟机的情况有不少相似之处,我们知道所有的运算操作都是由CPU完成的.CPU指令就牵扯到了数据的操作,难免就会和主内存打交道,虽然CPU发展的性能越来越好,但还是受限于内存的访问速度,通常这种差异是千差万别的,极端情况甚至上万倍. 基于上面原因,便有了加入缓存(CPU Cache)的机制,CPU Cache 充当CPU 和主内存的缓冲,就有了下面的操作 将数据先放到缓存中 CPU就可以直接拿缓存的数据计算处理 处理完之后就再放回主内存中 下面就是CPU的架构图

虽然这样解决速度的问题,但是也带来了另外一个问题,缓存一致性,举个例子如i++。 读取主内存的 i到CPU Cache中 对进行加一操作 将结果写到CPU Cache中 将数据刷新到主内存中。 i++在单线程不会有问题,但是多线程下就会有问题,假设i的初始值是0,两个线程本地都把i=0缓存到的CPU Cache中,然后都对i进行加一操作后,然后把他们放到主内存,就有可能存储的i还是1,这就是典型的缓存不一致. 这问题也有解决的办法,有两种: 通过总线加锁的方式 通过缓存一致性协议。 第一种是早期的解决办法, 他的原理就是一种悲观的实现,有多个CPU要和主内存交互的之前,都要和总线(数据总线,控制总线,地址总线)进行通信,总线只会允许一个CPU访问变量的主内存,这种效率太低,第二种,就是缓存一致性方式解决一致性问题,看一下的图,如下

缓存一致性最为出名的是Intel的MESI协议,保证了缓存使用中共享变量副本都是一致的,他的原理是: CPU 操作Cache中的数据 如果发现变量是共享变量,也就是说其他CPU中也有一个副本 那么当读取数据时,不做任何处理 如果是写操作,就是会让其他CPU的缓存失效,其他CPU就会从主内存再次获取。 理解了上面的内存,那对Java内存模型学习就很容易了,但是他们不是一个东西,我们先看一下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.这里我们不展开讲解,有兴趣的可以自行学习。 欢迎持续关注,后期会讲解一系类面试题,希望对大家有帮助。关注公众号获取相关资料请回复:typescript,springcloud,springboot,nodejs,nginx,mq,javaweb,dubbo,kafka,java面试题,ES,zookeeper,java入门到精通,区块链,java优质视频,大数据,kotlin,瞬间之美,HTML与CSS,深入体验java开发,web开发CSS系列,javaweb开发详解,springmvc,java并发编程,spring源码,python,go,redis,docker,即获取相关资料

扫码关注我们