Java内存模型

Java内存模型

概念

在特定操作协议下,对特定内存或高速缓存进行读写访问的过程抽象。

不同架构的物理计算机可以有不一样的内存模型,Java 虚拟机也有自己的内存模型。

Java 虚拟机规范中试图定义一种 Java 内存模型(Java Memory Model,简称 JMM)来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让 Java 程序在各种平台下都能达到一致的内存访问效果,不必因为不同平台上的物理机的内存模型的差异,对各平台定制化开发程序。

更具体一点说,Java 内存模型提出目标在于,定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节。

此处的变量(Variables)与 Java 编程中所说的变量有所区别,它包括了实例字段、静态字段和构成数值对象的元素,但不包括局部变量与方法参数,因为后者是线程私有的。

注:如果局部变量是一个 reference 类型,它引用的对象在 Java 堆中可被各个线程共享,但是 reference 本身在 Java 栈的局部变量表中,它是线程私有的。

Java内存模型的组成

主内存
主内存中存放了实例字段、静态字段和构成数组对象的元素、不包括局部变量和方法参数,因为后者属于线程私有的。主内存中存放共享的数据

工作内存
线程私有的

共享变量和共享变量副本
在这里插入图片描述

8种原子操作
  • lock(锁定):锁定主内存中的共享变量,每次只有一个线程能够访问
  • unlock(释放锁):释放锁,此操作之前必须有执行lock,lock和unlock是成对存在的。对共享变量添加了n个lock,就必须执行n个unlock的。
  • read(读取):从主内存中读取共享变量
  • load(加载):将从主内存中读取的共享变量加载到工作内存(线程)中
  • use(使用):将工厂内存中的变量传递给工作引擎
  • assign(赋值):从执行的工作引擎接收的值赋值给工厂内存的变量
  • store(存储):将工作内存中的变量传送到主内存中
  • write(写入):将工作内存传送过来的变量写到主内存中
使用8种原子操作需遵循的规则
  • 不允许read和load、store和write操作之一单独出现,即不允许从主内存中读取了但不加载到工作内存中,不允许工作内存发起回写但主内存不接受的情况。

  • 不允许一个线程丢弃它最近的assign操作,即变量在工作内存中发送了改变,需立即同步到主内存中。

  • 不允许一个线程无原因地(没有发生过assign操作)把数据从工作内存中同步到主内存中。

  • 一个新的变量只能在主内存中“诞生”,不允许在工作内存中直接使用未被初始化(load或assign)的变量,换句话说就是对一个变量实施use、store操作之前,必须先执行assign和load操作进行初始化。

  • 一个变量在同一时刻只允许一条线程对其进行lock操作,但lock操作可以被执行多次,多次执行lock后,只有执行多次unlock操作,变量才会被解锁。

  • 如果一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作以初始化变量的值。

  • 如果一个变量没有被lock操作锁定,那就不允许执行unlock操作,也不允许去unlock一个被其他线程锁定的变量。

注意:lock和unlock操作对应于synchronized关键字

指令重排序

指令重排序是指处理器(CPU)采用了允许将多条指令不按照程序规定的顺序分开发送给各个相应的电路单元进行处理。但并不是说指令任意排序,处理器必须能正确处理指令依赖情况保障程序能够得出正确的执行结果。
例如:

int a = 1;
a = a * 2;
int b = 2;


指令重排序可能会出现以下执行顺序:
int b = 2;
int a = 1;
a = a * 2;


不会出现以下执行顺序:
a = a * 2;
int a = 1;
int b = 2;
因为指令 a = a * 2; 依赖于 int a = 1;
原子性、可见性与有序性
  • 原子性:java内存模型通过原子操作包括read、load、use、assign、store、write这六个来保障原子性。我们可以大致认为,基本数据类型的访问、读写都是具备原子性的(例外:long和double的非原子性协定,只有在32位操作系统才有超低的概率发生,因为long和double都是64位的,32位操作系统会将long拆分成两个32位进行读写访问)。
  • 可见性:当共享变量在一个线程中改变了,其他线程能够马上发现此共享变量改变的值
  • 有序性:指令的有序性。单线程的情况下,所有操作都是有序的,多线程情况下,所有操作都是无序的。
volatile型变量的特殊规则

使用volatile的变量具有两个特性:

  • 保证此变量对所有线程的可见性
  • 禁止指令重排序优化
volatile的使用场景

满足以下其中一个,添加volatile即可达到线程安全:

  • 运算结果并不依赖变量的当前值,或者能够确保只有单个线程修改变量的值
  • 变量不需要与其他的状态变量共同参与不变约束
volatile在内存模型中的工作原理

标记volatile的变量,当在工作内存中变量发送改变时,会立刻同步到主内存中,其他线程如果需要使用此共享变量,会先从主内存中同步到工程内存中(保证了volatile的可见性)。

synchronized在内存模型中的工作原理

被synchronized标记的变量,在内存模型里是对变量执行lock操作,当执行lock操作后,必须遵循的规则->当一个变量执行的lock操作,将会清空所有工作内存中此变量的值,在执行引擎使用这个变量时,需要重新执行load或者assign操作以初始化变量的值。

先行发生原则

满足以下其中一个规则即可达到线程安全

  • 程序次序规则:在一个线程内,按照控制流顺序

  • 管程锁定规则:执行了lock操作,既添加了锁(synchronized)

  • volatile变量规则:在可以使用volatile场景下,使用volatile变量

  • 线程启动规则:Thread对象的start()方法先行发生于此线程的每一个动作。

  • 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程 的代码检测到中断事件的发生,可以通过Thread::interrupted()方法检测到是否有中断发生。

  • 线程终结规则:线程中的所有操作都先行发生于对此线程的终止检 测,我们可以通过Thread::join()方法是否结束、Thread::isAlive()的返回值等手段检测线程是否已经终止 执行。

  • 对象终结规则:一个对象的初始化完成(构造函数执行结束)先行发生于它的 finalize()方法的开始。

  • 传递规则:如果操作A先行发生于操作B,操作B先行发生于操作C,那就可以得出 操作A先行发生于操作C的结论。

依据先行发生原则来判断线程是否安全

参考文章

Java内存模型

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值