JVM之内存模型

Java内存模型其实就是围绕着在并发过程中如果解决原子性、有序性和可见性的通信规则

线程、主内存、工作内存三者之间的关系

主内存与工作内存

Java内存模型的主要目的就是定义程序中各种变量的访问规则,即关注在虚拟机中把变量存储到内存和从内存中取出变量这样的底层细节。

此处的变量指的是包括了实例字段,静态字段和构成数组对象的元素。但不包括局部变量和方法参数,因为后者是线程私有的。

  1. 主内存和工作内存的关系?

    Java内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存保存了被该线程使用的变量的主内存副本。线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的数据。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递需要通过主内存来完成。

  2. 主内存和工作内存如何交互?

    主内存和工作内存如何交互?即一个变量如何从主内存拷贝到工作内存、如何从工作内存同步回主内存。Java内存模型定义了8种操作来完成。分别是

    • lock(锁定)
    • unlock(解锁)
    • read(读取)
    • load(载入)
    • use(使用)
    • assign(赋值)
    • store(存储)
    • write(写入)

    他们每一种操作都是原子的、不可再分的(double和long类型,32位主机会拆分成两次执行)

    他们规定了八种执行规则,但这不是我们关心的重点,作为开发者更需要了解的是先行发生原则–用来确定一个操作在并发环境下是否安全

Volatile变量的特殊规则

Volatile是Java虚拟机提供的最轻量级的同步机制

为什么说他是最轻量级的同步机制?因为它只能保证可见性、有序性,而不能保证原子性。虽然应用场景有限,但是快,能保证其他线程的立即可见性。

当一个变量被定义成volatile之后,它具备两项特性:

  1. 保证此变量对所有线程的可见性,这里的可见性是指一条线程修改了这个变量的值,新值对于其他线程来说是立即得知的。由于volatile不能保证原子性(比如运算是分两步来做的),只能在以下两条规则的场景中进行运算。

    • 运算结果不依赖变量的当前值。
    • 变量不需要与其他的状态变量共同参与不变约束。

    简单点说,就是自己玩自己的,典型的引用场景就是作位状态标志的修饰符。

  2. 通过加入内存屏障禁止指令重排序,指令重排序优化是编辑器做的一种代码执行顺序的优化,只关注结果,不关注过程,但是这么做可能让程序逻辑混乱,比如本来应该在后面执行的代码,跑到前面来执行了,这时候就要使用volatile禁止指令重排序,从而保证代码的执行顺序和程序的顺序相同。

可见性、有序性、原子性

  1. 原子性

    Java内存模型直接保证的原子性变量操作包括read、load、assign、use、store、write这六个,我们大致可以认为,对基本数据类型的访问、读写都是具备原子性的。

    对于其他场景的原子性保证,Java内存模型提供了lock和unlock操作满足这种需求,反应到代码中就是synchronized关键字,因此synchronized是具备原子性的

  2. 可见性

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

    volatile的特殊规则保证了新值能够立即从工作内存同步到主内存,以及每次使用前从主内存刷新。

    synchronized是通过”对一个变量执行unlock操作之前,必须先把此变量同步回主内存中”实现的。

    final关键字修饰的字段在构造器中初始化完成,并且构造器没有把”this”的引用逃逸出去,那么在其他线程中就能看见final修饰字段的值。

  3. 有序性

    java程序中天然的有序性可以总结为一句话: 如果在本线程内观察,所有的操作都是有序的;如果在一个线程中观察另一个线程,所有的操作都是无序的。前半句是指 “线程内变量为串行语义”,后半句是指 “指令重排序” 现象和 “工作内存和主内存同步延迟” 现象。

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

    synchronized是由 “一个变量在同一时刻只允许一条线程对其进行lock操作” 这条规则获得的。

先行发生原则

先行发生是Java内存模型定义的两项操作之间的顺序关系,如果说操作A先行发生于操作B,其实就是说在发生操作B之前,操作A产生的影响被操作B观察到,”影响” 包括修改了内存中共享变量的值、发生了消息、调用了方法等。

Java内存模型中 “天然的” 先行发生关系包括以下八种,如果两个操作之间的关系不在此列,并且无法从下列规则推导出来,则他们就没有顺序型保障,虚拟机可以对它们进行任意的重排序

  1. 程序次序规则
  2. 管程锁定规则
  3. volatile变量规则
  4. 线程启动规则
  5. 线程终止规则
  6. 线程中断规则
  7. 对象终结规则
  8. 传递性
  • “时间上的先后顺序” 与 “先行发生” 之间有什么不同?

时间先后顺序与先行发生原则之间基本没有因果关系,所以我们衡量并发安全问题的时候不要受时间顺序的干扰,一切必须以先行发生原则为准。

Author: Tunan
Link: http://yerias.github.io/2020/03/26/jvm/4/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.