Fork me on GitHub

Java内存模型

主内存与工作内存

  • 主内存: Java内存模型规定了所有的变量都存储在主内存中。

    这里的主内存与物理硬件的主内存可以类比,但这里的主内存仅是指虚拟机内存的一部分。

  • 工作内存: 每条线程都有自己的工作内存,线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作(读取、赋值等)都必须在工作内存中,而不能直接读写主内存中的变量。

    工作内存可以与处理器的高速缓存类比

  • 线程、主内存、工作内存的交互关系

内存间交互操作

java内存模型定义的8种操作

  • lock(锁定): 作用于主内存的变量,它把一个变量标识为一条线程独占的状态
  • unlock(解锁): 作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
  • read(读取): 作用于主内存的变量,它把一个变量的值从主内存传输到线程的总做内存中,以便随后的load动作使用
  • load(载入): 作用于工作内存的变量,它把read操作从主内存中得到的变量值放入到工作内存给的变量副本中

    把一个变量从主内存复制到工作内存,那就要顺序地执行read和load,但不要求连续执行

  • use(使用): 作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到的变量的值的字节码指令时会执行这个操作
  • assign(赋值): 作用于工作内存的变量,它把一个从执行亲情接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作
  • store(存储): 作用于工作内存的变量,它把工作内存中一个变量的值传递给主内存中一遍随后的write操作使用
  • write(写入): 作用于主内存的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中

    把一个变量从工作内存同步到主内存,那就要顺序地执行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一个被其他线程锁定住的变量
  • 对一个变量执行unlock操作之前,必须先把此变量同步回主内存。

volatile型变量的特殊规则

volatile

当一个变量定义为volatile之后,它将具备两种特性:

  • 可见性: 当一条线程修改了这个变量的值,新值对于其他线程来说是可以立即得知的。而普通变量不能做到这一点,普通变量的值在线程间传递需要通过主内存完成

    volatile变量只能在以下两种场景保证可见性
    1.运算结果并不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值
    2.变量不需要与其他的状态变量共同参与不变约束

  • 禁止指令重排序优化: 普通变量仅仅会保证在该方法的执行过程中所有依赋值结果的地方都能获取到正确的结果,而不能保证变量复制的顺序与程序代码中的执行顺序一致。

volatile与锁之间的区别

volatile变量读操作的性能消耗与普通变量几乎没有什么差别,但是写操作则可能慢一些,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行,不过大多数场景volatile的总开销仍然要比锁低,我们在volatile与锁之间的选择的唯一依据仅仅是volatile的语义能否满足使用场景的需求

volatile变量的特殊规则

假设T表示一个线程,V和W分别表示两个volatile变量,在进行read、load、use、assign、store和write操作时需要满足如下规则:

  • 线程T对变量V的use动作可以认为是和线程T对变量的load、read动作的相关联,必须连续一起出现(即在工作内存中,每次使用V之前必须先从主内存刷新最新的值)
  • 线程T对变量V的assign动作可以认为是和线程T对变量的store、write动作的相关联,必须连续一起出现(即在工作内存中,每次修改V之后必须立刻同步回主内存中)
  • 假设A是T对V的use或assign动作,F是和A相关联的load或store动作,P是和F相关联的read或write动作;类似的B是T对W实施的use或assign动作,G是和B相关联的load或store动作,Q是和G相关联的read或write动作。 如果A先于B,那么P先于Q(这要求volatile变量不会被指令重排序优化)

原子性、可见性、有序性

原子性

  • 基本数据类型的访问读写是具备原子性的(long和double除外)
  • synchronized可以满足更大范围的原子性保证

    可见性

    可见性是指当一个线程修改了共享变量的值,其他线程能够立即得知这个修改

  • volatile: 保证多线程操作时变量的可见性而普通变量不可以
  • synchronized: 对一个变量执行unlock之前,必须先把此变量同步回主内存
  • final: 被final修饰的字段在构造器中一旦初始化完成,并且构造器没有把“this”的引用传递,拿在其他线程中就能看见final字段的值

    有序性

    如果在本线程内管程,所有的操作都是有序的(线程内表现为串行), 如果在一个线程中观察另一个线程,所有的操作都是无序的(指令重排序,工作内存与主内存同步延迟)

  • volatile关键字本身包含了禁止指令重排序的语义

  • synchronized决定了持有用一个锁的同步快只能串行地进行

“天然的“先行发生关系

  • 程序次序规则: 在一个线程内,按照控制流顺序发生
  • 管程锁定规则: 一个unlock操作先行发生于后面对同一个锁的lock操作
  • volatile变量规则: 对一个volatile变量的写操作先行发生于后面对这个变量的读操作
  • 线程启动规则: Thread对象的start()方法先行发生于此线程的每一个动作
  • 线程中止规则: 线程中的所有操作都先行发生于对此线程的终止检测
  • 线程中断规则: 对线程interrupt()方法的调动先行发生于被中断线程的代码检测到中断事件的发生
  • 对象终结规则: 一个对象的初始化完成先行发生于他的finalize()方法的开始
  • 传递性: A先行发生于B,B先行发生于C,则A先行发生于C

这些先行发生关系无须任何同步协助就已经存在,可以直接使用。如果两个操作之间的关系不在此列,并且无法从下列规则推导出来的话,它们就没有顺序性保障,虚拟机可以对它们随意地进行重排序。

-------------本文结束感谢您的阅读-------------