关于jvm:对-volatile-的理解

29次阅读

共计 2921 个字符,预计需要花费 8 分钟才能阅读完成。

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 屏障,确保以后线程在其余线程批改前获取最新的值

内存屏障

屏障执行程序解释
LoadLoadLoad1 -> LoadLoad -> Load2Load2 读取数据前,保障 Load1 读取数据读取结束
StoreStoreStore1 -> StoreStore -> Store2Store2 写入执行前,保障 Store1 写入对其余处理器可见
LoadStoreLoad1 -> LoadStore -> Store2Store2 写入执行前,保障 Load1 读取数据读取结束
StoreLoadStore1 -> StoreLoad -> Load2Load2 读取前,保障 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
正文完
 0