JAVA 内存模型(JMM)

一个人想着一个人 提交于 2020-08-05 11:16:31

什么是 JAVA 内存模型

Java 内存模型(Java Memory Model JMM)就是一种符合内存模型规范屏蔽了各种硬件和操作系统的访问差异、保证了 Java 程序在各种平台下对内存的访问都能保证效果一致机制及规范.

内存模型(Memory Model)

在了解 JAVA 内存模型之前让我们先来了解一下我们为什么需要内存模型.

为什么要有内存模型

我们都知道,计算机的每条指令是在 CPU 中执行的,而数据是存放在主存(计算器物理内存).计算机初期 CPU 的计算速率和内存读取写入数据的速度差不多,相安无事.但是随着 CPU 技术的高速发展,执行速度越来越快,内存数据读取写入跟不上 CPU 的处理速度,导致 CPU 每次操作都要耗费很长的时间等待数据获取.为了处理内存读取数据速率导致的性能瓶颈,在 CPU 和内存之间引入了高速缓存.

高速缓存: 一份数据拷贝,速度快,内存小.

程序执行过程也就变成了:高速缓存会将计算用的数据拷贝一份,CPU 直接从高速缓存获取数据.计算后向缓存插入数据,缓存内的数据再写入主存.

随着 CPU 速度的进一步提升,高速缓存也从原来的一层衍生为多级缓存.

多级缓存:按照数据读取顺序和 CPU 结合的紧密程度,分为一级缓存(L1),二级缓存(L2)......每一级缓存中所存储的全部数据都是下一级缓存的一部分.

加入多级缓存以后,自然程序执行的时候就变成了,CPU 从 L1 查询数据,无数据查询 L2,逐级往下查询的模式.

众所周知,CPU 是有单核和多核之分.在单核 CPU 的时候,只需要一套 L1,L2,L3 缓存.但是当多核时,每个核心都必须有自己的一套 L1(甚至 L2)缓存,而共享 L3(或 L2)缓存.

随着计算机能力的更进一步发展,从单线程,变成了多线程.这时候问题出现了.下面我们分析一下单线程,多线程单核,多核的情况.

  • 单线程:CPU 核心的缓存只有一个线程访问.缓存独占,不会出现数据访问冲突,数据不一致等问题.
  • 单核 CPU,多线程:进程中多线程同时访问缓存,获取的缓存地址相同,不会出现缓存丢失等问题,但是会出现,多线程同时操作同一缓存数据导致数据错误的情况.(CPU 处理非原子性,多线程互相争抢运行,会出现处理一半共享数据被其他线程更新的情况-->并发)
  • 多核 CPU,多线程:L3(共享缓存)情况如同单核多线程.单由于每个核心都有自己的 L1/L2,线程运行多核切换,多核内的缓存数据不一致.

也就是说,在多线程的情况下.缓存会出现缓存一致性问题

学过 JVM 的亲们应该都知道,JVM 的 JAVA 即时编译器(JIT)会做指令重排来提升性能.计算机硬件也有类似的功能-->处理器优化. 处理器优化会把指令重排,也就是执行的顺序不会按照我们代码编写顺序,而且乱序的.那么在并发编程的情况下,这就会导致数据处理和判断错误,得到不是我们希望的结果.

那么我们怎么处理这些问题呢

铛铛铛 --> 主角 内存模型出场

什么是内存模型

内存模型: 为了保证共享内存的正确性(可见性、有序性、原子性),内存模型定义了共享内存系统中多线程程序读写操作行为的规范.

本文主要讲解 java 内存模型,如果对内存模型的相关内容有兴趣,下篇 -->内存模型详解马上推出哦 对就是骗你关注.

JAVA 内存模型

真正的主角登场了 JAVA 内存模型 Java Memory Model (JMM)(本文为 JDK5 开始使用的内存模型)

JMM 规定了所有的变量都存储在主内存中(类似计算机内存吧),每条线程都有自己的工作内存(像不像高速缓存),线程中的工作内存中保存了该线程中用到的变量(主内存中的拷贝).线程对变量的所有操作都在工作内存中进行.线程之间工作内存不可互通.数据传递依靠主内存.多线程运行的时候就类似于一个单核多线程的 CPU 有没有.

Java 内存模型的实现

Java 中提供了一系列和并发处理相关的关键字,比如volatile、synchronized、final、concurren包等.其实这些就是 Java 内存模型封装了底层的实现后提供给程序员使用的一些关键字.

关键字太多,本文只对并发编程中要解决原子性有序性一致性的关键字的进行介绍.

原子性

众所周知,Java 中可以使用synchronized来保证方法和代码块内的操作是原子性的. synchronized的实现原理依赖于提供的两个高级的字节码指令monitorentermonitorexit.

可见性

Java 内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值,实现缓存数据等于主内存数据的方式来实现.

Java 中volatile关键字就提供了这种功能,被volatile修饰的变量,修改后会立即同步到主内存,在使用前,从主内存获取最新数据使用,而不是工作内存的缓存数据.这就保证,多线程情况下数据的可见性.

除了volatile,Java 中的synchronizedfinal两个关键字也可以实现可见性.只不过实现方式不同.

有序性

Java 中,可以使用synchronizedvolatiled 都可以保证多线程之间操作的有序性.

  • volatile关键字会禁止指令重排.
  • synchronized关键字保证同一时刻只允许单线程操作.

发现了没synchronized万能有没有.这么牛掰自然是要付出代价的.synchronized牺牲了性能保证了这么强大功能.所以在使用的时候,也是不能一切都加锁哦.毕竟加锁消耗性能,还有引入死锁等问题.比如无并行需要数据可见时,只需要volatile,不影响性能有满足要求.

写在文尾,队 JVM 有兴趣,推荐研读<深入浅出java虚拟机>

关注订阅号程序猿小哈,联系作者,加入学习交流群和小伙伴们一起学习进步

ht

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!