Files
Hui-s-notebook/Java 内存模型.md
2023-09-10 10:50:53 +08:00

3.9 KiB
Raw Blame History

主内存与工作内存

所有的变量都存储在主内存中, 每个线程还有自己工作内存, 线程对变量的所有操作在工作内存中进行,无法直接读取主内存, 无法访问对方工作内存中变量,线程间变量值传递需通过主内存

!Pasted image 20230620173539.png

内存间交互操作

主内存与工作内存之间交互协议有 8 种操作:

  • Lock 锁定 作用于主内存的变量, 把一个变量标识为一条线程独占状态
  • Unlock 解锁 作用于主内存变量, 把一个处于锁定的变量释放出来,释放后可被其它线程锁定
  • Read 读取 作用于主内存变量, 把一个变量的值从主内存传输到线程工作内存
  • Load 载入 作用于工作内存变量, 把 read 操作的值放入工作内存的变量副本中
  • Use 使用 作用于工作内存变量 把工作内存中的一个变量传递给执行引擎
  • Assign 赋值 作用于工作内存变量 把一个从执行引擎接收的值赋给工作内存的变量
  • Store 存储 作用于工作内存的变量 把工作内存中的变量传递到主内存
  • Write 写入 作用于主内存的变量 将 store 操作从工作内存获得的变量值放入主内存变量中

对于 volatile 变量的特殊规则

当一个对象被 volatile 定义后,将具备两项特性:

  1. 保证此变量对所有线程可见性, 可见性是指一条线程修改后,其它线程立刻可知, 由于 volatile 变量只能保证可见性, 在不符合一下规则时仍然需要加锁保证原子性:
  • 运算结果并不依赖当前值,或确保只能有单一线程修改变量值
  • 变量不需要与其他的状态变量共同参与不变约束
  1. 禁止指令重排序优化, 普通的变量仅会保证在该方法的执行过程中所有依赖赋值结果的地方都能获取到正确的结果,而不能保证变量赋值操作的顺序与程序代码中的执行顺序一致

针对 long 和 double 类型变量的特殊规则

long 和 double 的非原子性协议 对于 64 位的数据类型long 和 double 模型中定义了一条宽松的规定: 允许虚拟机将没有被 volatile 修饰的 64 位数据读写操作分为两次 32 位的操作来进行, 即允许虚拟机自行选择是否要保证 64 位数据类型 load、store、read、write 这四个操作的原子性

原子性、可见性与有序性

  1. 原子性 基本数据类型的访问、读写都是原子性的
  2. 可见性 当一个线程修改了共享变量的值时,其它线程能够立即得知这个修改, Volatile 保证了新值能够立即同步到主内存,以及每次使用前从主内存刷新, 除 volatile 外还有 synchronized 和 final 也可实现可见性
  3. 有序性 如果在本线程内观察,所有操作都是有序的; 如果在另一个线程中观察,所有操作都是无序的

先行发生原则

判断数据是否有竞争,线程是否安全的有效手段 先行发生是 Java 内存模型中定义的两项操作之间的偏序关系

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