java内存模型JMM


参考: 【教程】终于有人把Java内存模型说清楚了!

本文主要介绍 Java 内存模型: Java 内存模型是什么,为什么要有 Java 内存模型,Java 内存模型解决了什么问题等。

一、内存模型?

  • 内存模型:英文名 Memory Model,与计算机硬件有关。下面介绍下内存模型与硬件的关系。
  • java内存模型:Java Memory Model,简称JMM。

二、CPU 和缓存一致性

2.1 多级高速缓存诞生背景

  1. 计算机在执行程序的时候,每条指令都是在 CPU 中执行的,而执行的时候,又免不了和数据打交道。而计算机上面的数据,是存放在主存(计算机物理内存)当中的。
  2. 随着 CPU 技术发展,CPU 执行速度越来越快,而内存技术却止步不前。从内存中读写数据速度 与 CPU执行速度差距越来越大,导致 CPU 每次操作内存都很耗时。可是,不能因为内存的读写速度慢,就不发展 CPU 技术了吧?总不能让内存成为计算机处理的瓶颈吧?
  3. 所以,就在 CPU 和内存之间增加了高速缓存,用于保存一份数据拷贝。特点:速度快,内存小,价格贵。程序的执行过程就变成了:程序在运行过程中,会将运算需要的数据从主存复制一份到 CPU 的高速缓存当中。 CPU计算时,就直接读写高速缓存,运算结束后,再将高速缓存中的数据刷新到主存。
  4. 随着CPU能力提升,一层缓存慢慢无法满足要求,逐渐衍生出多级缓存。程序的执行就变成了:当 CPU 要读取数据时,先从一级缓存查找,没有则从二级缓存查找,还是没有则从三级缓存或内存查找。

2.2 结构划分:高速缓存、CPU、核

  • 单核CPU 只含有一套 L1,L2,L3 缓存;

  • 多核CPU 含多个核心,每个核心都含有一套 L1缓存(甚至还有 L2缓存),而共享 L3缓存。

  • 多个CPU 之间,相互没有共用资源,互不影响。

  • 下图为 “单CPU+双核” 的缓存结构:
    在这里插入图片描述

2.3 缓存一致性问题:多线程、多核CPU

随着计算机能力提升,开始支持多线程。单线程、多线程在单核 CPU、多核 CPU 中有什么影响呢?

  1. 单核/多核 + 单线程:CPU 核的缓存只被一个线程访问,没有访问冲突等问题。

  2. 单核CPU + 多线程:CPU 将某块内存加载到缓存后,不同线程在访问相同的物理地址时,都会映射到相同的缓存位置,这样即使发生线程的切换,缓存仍然不会失效。由于任何时刻只能有一个线程在执行,因此不会出现缓存访问冲突。

  3. 多核 CPU + 多线程:每个CPU有多个核,每个核都至少有一个 L1 缓存,多个线程访问进程中的某个共享内存,这多个线程分别在不同的核心上执行,则每个核心都会在各自的缓存中保留一份共享内存的缓冲。
    缓存一致性问题由于多核是可以并行的,可能会出现多个线程同时写各自缓存的情况,而各自缓存 之间的数据就可能不一致。

如图举例:合伙人 1 要辞退员工 a,合伙人 2 要给员工 a 升职,升职后的话他再被辞退需要多个合伙人开会决议。两个合伙人分别把命令下发给了自己的管理人员。合伙人 1 命令下达后,管理人员 a 在辞退了员工后,他就知道这个员工被开除了。而合伙人 2 的管理人员 2 这时候在没得到消息之前,还认为员工 a 是在职的,他就欣然的接收了合伙人给他的升职 a 的命令。
在这里插入图片描述

三、处理器优化与指令重排

  • 处理器优化(硬件):为了使处理器内部的运算单元能够被充分利用,处理器可能会对输入代码进行乱序执行处理,即处理器优化。
  • 指令重排:比如 Java 虚拟机的即时编译器(JIT)会做指令重排。如果任由处理器优化和编译器对指令重排的话,就可能导致各种各样的问题。

四、并发编程问题:原子性、可见性、有序性

硬件和软件有啥关系?以并发编程视角来看,并发编程为了保证数据的安全,需满足三个特性:

  1. 原子性,Atomicity,【对应处理器优化问题】,是指在一个操作中,CPU 不可以在中途暂停然后再调度,即要不执行完成,要不就不执行。
    在 Java 中,为了保证原子性,提供了高级的字节码指令 Monitorenter 、 Monitorexit。这两个字节码,在 Java 中对应的关键字就是 synchronized。(参见博客:synchronized原理)。因此,在 Java 中可以使用 synchronized 来保证方法和代码块内的操作是原子性的。
  2. 可见性,Visibility,【对应缓存一致性问题】,是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
    Java内存模型实现可见性的方式:通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值。普通变量与volatile变量的区别?volatile的特殊规则保证了新值能立即同步到主内存,以及在使用前立即从内存刷新。所以,volatile保证了线程操作时变量的可见性,而普通变量则不能保证这一点。
  3. 有序性,Ordering,【对应指令重排问题】,即程序执行的顺序按照代码的先后顺序执行。
    在 Java 中,可以使用 synchronized 和 volatile 来保证多线程之间操作的有序性。实现方式有所区别:volatile 关键字会禁止指令重排。synchronized 关键字保证同一时刻只允许一条线程操作。

五、内存模型解决了什么?

  • 为了保证并发编程中共享内存的正确性(可见性、有序性、原子性),提出了内存模型。
  • 内存模型定义了共享内存系统中多线程程序读写操作行为的规范,通过这些规则来规范对内存的读写操作,从而保证指令执行的正确性。它与处理器有关、与缓存有关、与并发有关、与编译器也有关。

因此,内存模型解决了 CPU 多级缓存、处理器优化、指令重排等导致的内存访问问题,保证了并发场景下的一致性、原子性和有序性。

内存模型解决并发问题主要采用两种方式

  • 限制处理器优化
  • 使用内存屏障

六、Java 内存模型

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

Java 内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存

线程的工作内存中保存了该线程中用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存

不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进行。

而 JMM 就作用于工作内存和主存之间数据同步过程。它规定了如何做数据同步以及什么时候做数据同步。
在这里插入图片描述
这里面提到的主内存和工作内存,读者可以简单的类比成计算机内存模型中的主存和缓存的概念。

主内存和工作内存与 JVM 内存结构中的 Java 堆、栈、方法区等并不是同一个层次的内存划分,无法直接类比。

《深入理解Java虚拟机》中认为:从变量、主内存、工作内存的定义来看,主内存主要对应于 Java 堆中的对象实例数据部分。而工作内存则对应于虚拟机栈中的部分区域。

七、Java 内存模型的实现

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

在开发多线程的代码的时候,我们可以直接使用 synchronized 等关键字来控制并发,这样就不需要关心底层的编译器优化、缓存一致性等问题。

所以,Java 内存模型,除了定义了一套规范,还提供了一系列原语,封装了底层实现后,供开发者直接使用

八、总结

好了,这里简单的介绍完了 Java 并发编程中解决原子性、可见性以及有序性可以使用的关键字。

读者可能发现了,好像 synchronized 关键字是可以同时满足以上三种特性的,这也是很多人滥用 synchronized 的原因。
但是 synchronized 是比较影响性能的,虽然编译器提供了很多锁优化技术,但是也不建议过度使用。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

攻城有术

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值