Java内存模型(JMM)

本文深入探讨Java内存模型(JMM)的实现原理,包括平台内存模型、JMM与线程工作内存的关系,以及JMM对volatile变量的特殊处理规则。通过理解JMM,可以更好地掌握Java并发编程。

1. Java内存模型的实现

1.1 平台(计算机)内存模型

  计算机模型是一个与硬件相关的概念,它的起源较早。当计算机刚出现的时候,内存的速度还跟的上处理器的速度,处理器可以直接访问主内存。随着处理器的发展,处理器的执行速度越来越快,而内存的访问速度已经远远跟不上处理器的执行速度(内存访问速度不是不可以提升,只是提升的成本太高,是提升处理器执行速度的好几倍),于是在每个处理器的内部增加了高速缓存(其中可细分为一级、二级、三级缓存),其中访问一级缓存的速度接近于处理器的速度(其中处理器内部的寄存器速度更接近于处理器的速度),使用高速缓存可以减少处理器访问主内存的频率(无论读写)。但是,由于高速缓存在处理器的内部,每个处理器的高速缓存不能访问到其他处理器的高速缓存。如果不同处理器的高速缓存涉及到对同一块主内存的修改,就会产生数据不一致的问题,这就是著名的缓存一致性问题。于是不同的平台会在高速缓存与主内存之间增加缓存一致性协议,以解决缓存一致性问题。

1.2 Java内存模型

  实际计算机中会遇到的问题,虚拟机中同样会发生。JVM中每个线程都拥有自己的工作内存,可类比于处理器的高速缓存,实际上线程的工作内存会优先存储于处理器的寄存器与高速缓存中,而对主内存的访问也同样会遇到缓存一致性的问题,并且不同的计算机平台的内存访问存在差异,而Java是跨平台的。为了解决这种差异,使得Java程序在不同的平台中达到一致的内存效果,为此,JVM制定了一组规范,这些规范定义了一个一致的、跨平台的内存模型,就是Java内存模型(JMM)。
  JMM规定了所有的变量(此处变量不包括局部变量,因为局部变量是线程私有的,不共享,包含的对象的属性以及构成数组对象的元素)都存储在主内存中,每个线程都有自己的工作内存,线程间的工作内存不共享。所有线程对变量的操作都必须在自己的工作内存中进行(线程首先会从主内存中拷贝一份变量的副本至自己的工作内存中,修改后存储到主内存中,在这里拷贝时不会拷贝变量的全部,而只是拷贝需要的部分),而不允许直接操作主内存。线程间的通信(这里的线程通信指的是多个线程对同一个共享变量的访问)都通过共享的主内存来完成。
  所以JMM实际上定义的是程序中变量的访问规则,即JVM如何从主内存中拷贝变量以及如何将变量存储到主内存中的底层细节。这个底层细节实际上就是线程工作内存与主内存之间的交互。

2. 线程工作内存与主内存的交互 在这里插入图片描述

  以下所有内容均来自周志明所著《深入理解Java虚拟机》,并在此基础加以自己的理解并总结。
  Java内存模型定义了以下8种操作来完成线程工作内存与主内存的交互。虚拟机的实现保证以下8种操作都是原子的、不可再分的。而对于64位的数据类型(long和double),允许虚拟机将没有被volatile修饰的64位数据的读写操作划分为两次32位操作来进行,即允许虚拟机实现选择不保证64位数据类型的load、store、read和write这四个操作的原子性,这点即所谓的long和double的非原子性协定。但目前各种平台下的商用虚拟机几乎都选择把64位数据的读写操作作为原子操作来对待。
  lock(锁定):作用于主内存的变量,它把一个变量标识为一条线程独占的状态。
  unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
  read(读取):作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用。
  load(载入):作用于工作内存的变量,它把read操作从主内存中得到的值放入工作内存的变量副本中。
  use(使用):作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作。
  assign(赋值):作用于工作内存的变量,它把一个执行引擎接收到的值赋给工作内存中的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
  store(存储):作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中,以便随后的write操作使用。
  write(写入):作用于主内存的变量,它把store操作工作内存中得到的值放入主内存的变量中。

  Java内存模型规定在执行上述8种操作时必须满足以下规则:
  一个变量从主内存复制到工作内存,那就要顺序地执行(不保证是连续执行)read和load操作;一个变量从工作内存同步回主内存,就要顺序地执行(不保证是连续执行)store和write操作。
  不允许read和load、store和write操作之一单独出现,即不允许一个变量从主内存读取了但工作内存不接受,或者从工作内存发起回写了但主内存不接受的情况出现。
  不允许一个线程丢弃它的最近的assign操作,即变量在工作内存中改变了之后必须把该变化同步回主内存。
  不允许一个线程无原因地(没有发生过任何assign操作)把数据从线程的工作内存同步到主内存中。
  一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load或assign)地变量,即对一个变量实施use、store操作前必须先分别执行了load和assign操作。
  一个变量在同一时刻只允许一条线程对其进行lock操作,但lock操作可以被同一条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁。
  如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作初始化变量的值。
  如果一个变量事先没有被lock操作锁定,那就不允许对它执行unlock操作,也不允许去unlock一个被其他线程锁定住的变量。
  对一个变量执行unlock操作之前,必须先把此变量同步回主内存中(执行store、write操作)。

3. 对于volatile型变量的特殊原则

  Java内存模型对volatile变量定义的特殊规则如下:
  线程对volatile变量的use动作可以认为是和线程对volatile变量的load、read动作相关联,必须连续一起出现。这条规则要求在工作内存中,每次使用volatile变量前必须从主内存中刷新最新值,用于保证能够看见其他线程对volatile变量所作的修改后的值。
  线程对volatile变量的assign动作可以认为是和线程对volatile变量的store、write动作相关联,必须连续一起出现。这条规则要求在工作内存中,每次修改volatile变量的值后必须立刻同步回主内存,用于保证其他线程可以看到自己对volatile变量所做的修改。

  假定V、W分别表示两个volatile变量。动作A是线程T对变量V实施的use(assign)操作,动作F是和动作A相关联的load(store)操作,动作P是和动作F对应的变量V的read(write)动作;类似的,动作B是线程T对变量W实施的use(assign)操作,动作G是和动作B相关联的load(store)操作,动作Q是和动作G对应的变量W的read(write)动作。如果A先于B,那么P先于Q。这条规则要求volatile修饰的变量不会被指令重排序优化,保证代码的执行顺序与程序的顺序相同。

  参考:《深入理解Java虚拟机》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值