12.1、JVM 学习——Java 内存模型与8种基本操作、原子性、可见性、有序性

前言

体能状态先于精神状态,习惯先于决心,聚焦先于喜好。

Java 内存模型

Java 作为扩平台的语言,其通过Java 虚拟机对字节码文件进行运行时编译实现跨平台的特性。
Java 支持多线程,由此,虚拟机内存的管理问题,尤其是多线程操作间内存的管理问题需要一个足够严谨又足够宽松的 Java 内存模型,使得“并发内存访问操纵不会产生歧义,同时虚拟机的实现可以有足够自由的空间去利用各种硬件特性(寄存器、高速缓存和指令集中的某些特有属性)。

JDK 1.5 和 Java 内存模型

JDK 1.5 是Java 发展史上的一个里程碑版本,许多新特性从这一版本开始出现。
在JDK1.5发布之后,Java 内存模型已经基本成熟和完善起来了。
Java 内存模型将内存分为主内存和工作内存,主内存为所有线程共有,工作内存为每个线程各自拥有,通过8个基本命令进行内存间的交互。
Java 内存模型如下图:

在这里插入图片描述

主内存和工作内存

Java 内存模型将内存分为所有线程共享的主内存和每个线程私有的工作内存。
Java 内存模型并没有限制执行引擎使用处理器的特定寄存器或缓存来和主内存进行交互,也没有限制即时编译器进行调整代码执行顺序这类优化措施。

所有的公共变量都需要在主内存存储

Java 内存模型中的变量和Java 编程中的变量有所区别,它包括了实例字段、静态字段和构成数组对象的元素。

工作内存存储主内存的备份和线程私有的信息

工作内存一方面需要存储线程私有化的信息——比如局部变量、方法参数等,再一个还需要存储一份其用到的主内存中的变量的拷贝(注意是用到啥就拷贝啥,节约空间)。

主内存和工作内存的相互关系

线程对内存的所有操作都是通过工作内存来进行的,工作内存和主内存存在对应的同步规则。
工作内存之间必须通过主内存来传递变量的值,工作内存之间无法直接访问对方。

Java 内存模型的8种基本操作

Java 内存模型规定了8种基本操作,来确内存操作的安全和高效。
基本操作之间是有先后顺序的,用于确保可见性、原子性和有序性

主内存的操作
  • lock 锁定:主内存,将一个变量标识为某线程独有。
  • unlock 解锁:主内存,将一个锁定状态的变量解除锁定,其他变量可以占用。
工作内存的操作——和执行引擎的交互
  • use 使用:作用于工作内存,将变量传递给执行引擎。
  • assign 赋值:作用域工作内存,将执行引擎的执行结果赋值给工作内存中的变量。
主内存和工作内存的同步操作
主内存到工作内存
  • read 读取:作用于主内存,将主内存的变量传输到工作内存中
  • load 载入:作用于工作内存,将 read 操作读取到的值放入工作内存的副本中
工作内存到主内存
  • store 存储:作用于工作内存,将工作内存中的一个变量的值传递给主内存
  • write 写入:作用于主内存,将store传递来的值保存到主内存的一个变量中。

Java 内存模型围绕 原子性、可见性和有序性建立

Java内存模型是围绕是围绕着在并发过程中如何处理原子性、可见性和有序性3个特征来建立的。
基于8个基本命令、volatile关键字、synchronized 的基本理解

原子性的实现
  • Java 内存模型要求 lock、unlock、read、load、assign、use、store、write 这8个基本操作都具有原子性,对于 64位数据类型( long和double),当其没有被 volatile 修饰时,允许对其的读写操作转换为两次32位的操作。——但是其实一般商业虚拟机在实现上依旧保证了64位数据操作的原子性。但是在多线程场景下,64位的数据类型或许会有操作风险,因为一个数的变更被分为了两个部分——根据具体虚拟机来定。
  • synchronized 关键字修饰的代码也具有原子性操作的性质,其对应的字节码指令为 monitorenter 和 monitorexit,这两个字节码对应的正式基本命令中的 lock和unlock
可见性的实现
  • 可见性是指当一个线程修改了共享变量的值,其他线程能够立即得知这个修改。具体来说就是本线程修改某变量值后立即同步到主内存,其他线程的备份会过期,在使用时会再次从主内存获取最新值。
  • Java 内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方式来实现可见性的。无论是普通变量还是volatile修饰的变量都是如此。
  • volatile 和可见性。被volatile 修饰的变量,其修改后会被立即同步到主内存,并且每次线程使用前都需要从主内存刷新一下,而普通变量不能保证这一点——修改的值立即同步到主内存。
  • synchronized 和可见性。synchronized最终实现是基于Java 内存模型中的基本命令 lock和unlock的,对于一个变量在执行 unlock 操作之前,必须先把此变量同步回主内存,从而确保了可见性。
  • final 和可见性。被 final 修饰的字段在构造器中一旦初始化完成,并且构造器没有把“this”引用传递出去,那在其他线程中就能看到final修饰字段的值。(这里有一个引用逃逸的问题,就是你不想暴露这个对象给外部使用,但是外部可以访问就是逃逸了,这里是为了确保初始化完成,这样final修饰的变量才有值)
JIT 优化和可见性

在 HotSpot 虚拟机中,对于普通变量,当在Server 模式下运行时,由于JIT优化会导致主线程修改的变量无法及时被其他线程感知——失去可见性。
这个时候可以使用Client模式,或者使用volatile修饰。

有序性:本线程的有序和其他线程的相对无序
  • Java 程序中天然的有序性可以总结为:如果在本线程内观察,所有操作都是有序的;如果在本线程观察另一个线程,所有的操作都是无需的。
  • 本线程有序是由于“线程内串行语义”,其他线程无序是因为“指令重排序”和“工作内存和主内存的同步延迟”。
  • volatile 可以禁止指令重排。
  • synchronized 是通过每次只允许一个线程运行来实现有序性的。

参考资料

[1]、https://blog.csdn.net/lsxf_xin/article/details/79712537
[2]、《深入理解Java 虚拟机》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值