# Java 内存模型

# 什么是Java内存模型?

一种多线程访问Java内存的规范。

将内存分为了 主内存 和 工作内存 定义了几个原子操作,用于操作主内存和工作内存中的变量
定义了volatile变量的使用规则

  • happens-before,即先行发生原则

只要符合这些规则,则不需要额外做同步措施 如果某段代码不符合所有的happens-before规则,则这段代码一定是线程非安全的

# 内存间的交互操作有哪些,规则是什么?

  • lock (锁定) 作用于主内存的变量,把一个变量标识为一条线程独占状态
  • unlock (解锁)
  • read (读取)
    作用于主内存变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
  • load (载入)
    作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
  • use (使用)
    作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎, 每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。
  • assign (赋值)
    作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量
    每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作
  • store (存储) 作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作。
  • write (写入) 作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中。

规则

  1. 不允许read和load、store和write操作之一单独出现
  2. 不允许一个线程丢弃它的最近assign的操作,即变量在工作内存中改变了之后必须同步到主内存中。
  3. 不允许一个线程无原因地(没有发生过任何assign操作)把数据从工作内存同步回主内存中 一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量。 即就是对一个变量实施use和store操作之前,必须先执行过了assign和load操作。
  4. 一个变量在同一时刻只允许一条线程对其进行lock操作 但lock操作可以被同一条线程重复执行多次,
    多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁,lock和unlock必须成对出现。
  5. 如果对一个变量执行lock操作 将会清空工作内存中此变量的值 在执行引擎使用这个变量前需要重新执行load或assign操作初始化变量的值。
  6. 如果一个变量事先没有被lock操作锁定 则不允许对它执行unlock操作;也不允许去unlock一个被其他线程锁定的变量。
  7. 对一个变量执行unlock操作之前,必须先把此变量同步到主内存中(执行store和write操作)。

# 什么是重排序?

从Java源代码到最终实际执行的指令序列,会经过下面三种重排序:

  • 编译器优化的重排序 编译器在不改变单线程程序语义放入前提下,可以重新安排语句的执行顺序。

  • 指令级并行的重排序 现代处理器采用了指令级并行技术来将多条指令重叠执行。
    如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。

  • 内存系统的重排序 由于处理器使用缓存和读写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。

为了保证内存的可见性,Java编译器在生成指令序列的适当位置会插入内存屏障指令来禁止特定类型的处理器重排序
Java内存模型把内存屏障分为LoadLoad、LoadStore、StoreLoad和StoreStore四种。

# 对于volatile型变量的特殊规则

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

  1. 保证此变量对所有线程的可见性 由于valatile只能保证可见性,在不符合以下两条规则的运算场景中,我们仍要通过加锁来保证原子性。
    (1)运算结果并不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值。
    (2)变量不需要与其他的状态变量共同参与不变约束。

  2. 禁止指令重排序
    普通的变量仅仅会保证在该方法的执行过程中所有依赖赋值结果的地方都能获取到正确的结果,
    而不能保证变量赋值操作的顺序与程序代码中执行顺序一致,这个就是所谓的线程内表现为串行的语义。

# 对于long和double型变量的特殊规则

对于64为的数据类型(long和double),在模型中特别定义了一条相对宽松的规定。
允许虚拟机将没有被volatile修饰的64位数据的读写操作分为两次32为的操作来进行。
即允许虚拟机实现选择可以不保证64位数据类型的load、store、read和write这4个操作的原子性。

# 原子性、可见性和有序性

原子性: 即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
可见性: 可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
有序性: 即程序执行的顺序按照代码的先后顺序执行。

# 先行发生原则

这些先行发生关系无须任何同步就已经存在,如果不再此列就不能保障顺序性,虚拟机就可以对它们任意地进行重排序。

  • 程序次序规则 在一个线程内,按照程序代码顺序,书写在前面的操作先行发生于书写在后面的操作。
    准确的说,应该是控制顺序而不是程序代码顺序,因为要考虑分支。循环等结构。

  • 管程锁定规则 一个unlock操作先行发生于后面对同一个锁的lock操作。这里必须强调的是同一个锁,而后面的是指时间上的先后顺序。

  • Volatile 变量规则
    对一个volatile变量的写操作先行发生于后面对这个变量的读操作,这里的后面同样是指时间上的先后顺序。

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

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

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

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

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