乐趣区

关于jvm:对-volatile-的理解

NOTICE:本文仅记录自己对 volatile 关键字的小小了解,没有具体记录每个点,若有误可指出

一个对象的产生

java 的 Class 对象产生会经验以下阶段:类加载,验证,筹备,解析,初始化

  1. 类加载:通过类的全限定名获取类的二进制,并转换成 JVM 的办法区的 Class 对象
  2. 验证:对 Class 对象进行格局上的验证,别离有文件格式验证,元数据验证,字节码验证,符号援用验证
  3. 筹备:给 Class 对象的 static 变量分配内存并赋初始零值
  4. 解析:姜符号援用转换成间接援用
  5. 初始化:执行 Class 文件显式给 static 变量赋值语句

若运行时须要应用 Class 对应的对象时,会应用 new 关键字或者 newInstance 办法创立,于是 JVM 调用 Class 的元信息,在堆,运行时常量池划分一块内存放入新建的对象

如果对象是在虚拟机栈上,应用的是局部变量,那程序始终执行上来,没问题。

然而如果应用的是成员变量,并发批改,并且想要看到是对的(可见性),那别的批改须要批改后写回到主存,并且在用的时候也要拉到最新的数据,这波及到 JAVA 的内存模型,以及缓存一致性协定

JAVA 内存模型

JAVA 内存模型次要分为两类:主内存和工作内存

主内存是所有变量存储的中央,工作内存是线程具体工作的中央,应用的是主内存的变量正本

这里就波及主内存与工作内存的同步问题,波及到并发三个性以及内存间交互操作

并发过程的三个性

原子性

一个操作 / 多个操作要么执行胜利,要不都不执行,相似于事务

可见性

一个线程对变量进行操作,其余线程能立即看到变更,这个就解决了变更后线程间不统一的问题

有序性

程序执行程序依照控制流程序执行

内存间交互操作

  • lock

[主内存] 将某个变量标为该线程独占的状态

  • read

[主内存 -> 工作内存] 将主内存中变量的值 copy 到工作内存中

  • load

[工作内存] 将 read 过程中变量的值赋给变量正本

  • use

[工作内存] 代码内应用变量

  • assign

[工作内存] 将代码过程中变更的值赋给工作内存变量正本

  • store

[工作内存 -> 主内存] 将工作内存变量正本的值传回到主内存

  • write

[主内存] 将传回来的值写回到主内存变量中

  • unlock

将变量从独占状态开释

  • 概括来说

一个线程应用某个变量,赋值给某个变量,须要在主内存,工作内存中相互复制,必须要通过的步骤:read -> load -> use -> assign -> store -> write

若想线程想独占这个变量,两个形式:该变量是局部变量,用 volatile 润饰该全局变量

缓存一致性协定

MESI

MESI 是一种基于回写(write-back)、缓存有效化(invalidate)的协定

状态机
  1. Modify
  2. Exclusive
  3. Shared
  4. Invalid
状态变更(缓存 A / 缓存 B / 主存)
状态变更 前提 动作
Modify -> Modify / local read/local write,状态不发生变化
Modify -> Invalid 缓存 A / B 同时含有数据 前一时间点缓存 A 已更新,缓存 B 接管 Invalid message,将该缓存置为 Invalid
Modify -> Shared 缓存 A 更新了本地数据,缓存 B 无数据 缓存 A write back 到主存,缓存 B 拉取主存最新数据
缓存 A 数据从 Modify -> Shared
状态变更 前提 动作
Exclusive -> Exclusive / 缓存 A local read
Exclusive -> Modify 缓存 A 缓存 Exclusive 缓存 A local write
Exclusive -> Shared 缓存 A 缓存 Exclusive 缓存 B 读主存数据,数据从 Exclusive 变为 Shared
Exclusive -> Invalid 缓存 A 缓存 Exclusive 缓存 A local write,将数据置为 Invalid
状态变更 前提 动作
Shared -> Shared / 缓存 A local read / 缓存 A/B 同时读同一份数据
Shared -> Invalid 缓存 A 批改了缓存 缓存 B 接管 Invalid 事件,并将本身置为 Invalid
Shared -> Modify / 缓存 A local write
状态变更 前提 动作
Invalid -> Invalid 缓存 B 缓存 Invalid 缓存 A Modify 缓存,缓存 B 缓存从 Invalid 到 Invalid
Invalid -> Shared / Exclusive / 缓存 B 拉取最新的数据,若缓存 A 有数据,则为 Shared,不然为 Exclusive
Invalid -> Modify / 缓存 A 拉取最新数据,并 local write,状态为 Modify

缓存一致性在 JVM 中落地 — volatile 关键字

次要个性

  1. volatile 润饰的变量的批改对于所有线程具备可见性

    1. volatile 通过在操作变量前后插入内存屏障
    2. volatile 润饰的变量在 assign 并 write 回到主内存后,告诉其余线程值被扭转,具体步骤参考 MESI 协定的状态流转
  2. volatile 禁止机器指令重排序
  3. volatile 不保障原子性

    1. 若前一线程读取变量后被阻塞,后一线程批改后写回主存,前一线程后续批改后写回主存,就呈现主存的数据不统一的景象

实用场景

  1. 一次性重要事件,比方程序敞开赋值某个 boolean 变量
  2. double check lock,避免指令重排序导致拜访到未初始化对象

底层实现

volatile 通过内存屏障实现可见性

写操作

写操作前插入 StoreStore 屏障,确保批改对其余线程可见

写操作后插入 StoreLoad 屏障,确保其余线程在读取数据时能读取最新的数据

读操作

读操作前,插入 LoadLoad 屏障,确保所有线程拿到的数据都是一样的

读操作后,插入 LoadStore 屏障,确保以后线程在其余线程批改前获取最新的值

内存屏障

屏障 执行程序 解释
LoadLoad Load1 -> LoadLoad -> Load2 Load2 读取数据前,保障 Load1 读取数据读取结束
StoreStore Store1 -> StoreStore -> Store2 Store2 写入执行前,保障 Store1 写入对其余处理器可见
LoadStore Load1 -> LoadStore -> Store2 Store2 写入执行前,保障 Load1 读取数据读取结束
StoreLoad Store1 -> StoreLoad -> Load2 Load2 读取前,保障 Store1 写入对所有处理器可见

指令重排序

CPU 为了运行效率,会对指令进行重排序,前面代码可能会先于后面的代码执行

重排序也遵循 as-if-serial 语义以及 happen-before 准则

as-if-serial 语义

存在数据依赖关系的先后操作不会重排序

happen-before 准则

后行产生准则,后行产生的操作产生的影响能被后续的操作获取

分类 阐明
程序秩序规定 控制流程序,后面代码肯定会先于前面代码执行
管程锁定准则 锁的 lock 操作先于 unlock 操作执行
volatile 变量准则 volatile 变量的写操作先于读操作执行
线程启动准则 线程的 start 办法先于线程任一操作执行
线程终止准则 线程任一操作先于对线程的终止操作
线程中断准则 线程的 interrupt 办法调用先于线程任一检测中断事件操作
对象终结准则 对象的初始化实现先于 finalize 办法的调用
传递性 A -> B,B -> C,那么 A -> C
退出移动版