java内存模型(JMM)以及volatile变量、happens-before、as-if-serial相关规则说明

一、处理器与存储设备运算效率以及数据一致性

1.1、处理器与存储设备运算速度差

  在计算机中,绝大多数的任务处理都是处理器、内存、磁盘等配合完成,不可能单靠寄存器完成所有的任务。而这些硬件中,处理器的运行速度又是遥遥领先于内存、磁盘等的IO操作

1.2、解决方案

  为了解决处理器与内存的效率差的问题,现代计算机在处理器的每个内核多增加高速缓存(如下图)
在这里插入图片描述

1.3、存在的问题

  增加了高速缓存后就会存在处理器多个核间缓存一致性的问题,为了解决这个问题,处理器内核间都需要遵循一些一致性协议,如MSI/MESI/MOSI/Synapse/Firefly/Dragon/Protocal等

二、java内存模型

2.1 线程、工作内存和主内存的关系

  A、在java模型中规定了所有的变量值都存储主内存(物理内存)中,而java模型的目的是规定变量的访问规则。
  B、线程对所有变量的读取、赋值等操作都是在工作内存(寄存器/高速缓存)中进行,这里的工作内存可对应虚拟机栈的一部分
  C、java模型所规定的作用范围主要是实例变量、静态变量等可被多线程共享的变量
在这里插入图片描述

2.2 内存间的数据交互过程

2.2.1、数据交互过程

  java内存模型为一个变量的操作规定了8种原子操作(如下图):
在这里插入图片描述
  A、read:读取主内存的变量
  B、load:把read操作从主内存获取到的变量加载到工作内存中
  C、use:把工作内存中的变量传递给执行引擎,比如虚拟机栈中某个操作需要使用到该变量的值时会用到这个操作
  D、assign:执行引擎把接收到的值赋给工作内存中的变量
  E、store:把工作内存中的变量值传递给主内存
  F、write:接收工作内存store过来的变量值写入到主内存中
  G、lock:把主内存中的变量标识为某个线程独占状态,主要是在对象头的Mark Word中记录当前独占线程信息以及锁状态等
  H、unlock:把主内存中被当前线程独占的对象释放掉,以便于其他线程锁定

  注:以上的图显示的结果是线程1先执行后,线程2隔一定时间再执行的结果,如果线程1和线程2同时执行或者间隔较短时间执行会由于可见性和竞争而存在线程安全的问题,也就是当read的值都是0时,最终的结果会是data=1,如果只需要可见性,可用volatile或者synchronized解决,如果需要保证线程安全,需要通过synchronized(虚拟机没有直接提供lock和unlock指令操作,但提供了monitorenter和monitorexit指令,同步块就是使用这两指令实现)或者java并发包下的lock进行加锁实现。

2.2.2 数据交互的规则与限制

  A、一个变量在主内存和工作内存间流转操作是成对顺序出现的,如read/load、store/write,不允许单一的存在
  B、不允许线程丢弃assign操作的结果,也即工作内存中发生改变的变量值需要同步回主内存,没有发生改变不允许同步回主内存
  C、所有变量只在主内存中初始化,也就是如上图,所有的工作内存中的操作,右侧的操作必须发生在左侧操作之后
  E、一个变量同一时间只允许一个线程lock,但可以lock多次,但对应的unlock次数与lock次数一致时才被释放;每一个unlock前都必须现有一个lock;一个变量在lock前会清空工作内存中改变量的值,unlock前必须同步回主内存

2.3 volatile修饰变量的特殊规则

  当一个变量被volatile修饰后,多线程中这个变量将具有可见性和有序性(禁止指令重排序)的能力:
  A、可见性:当变量被volatile修饰后,线程每次操作该变量的值都需要从主内存读取,而线程对该变量的值进行改变后,会及时从工作内存同步回主内存
  B、禁止指令重排序:指令重排序是处理器对于程序执行顺序的优化,当程序上下文没有依赖关系时,执行顺序可能会由于优化而发生改变,当增加volatile进行修饰后,相当于该变量在读写位置增加内存屏障,禁止该变量读写指令前后的其他指令与该指令进行重排,而读取该变量时强制从主内存进行读取,保证多个线程操作该变量时数据的一致性。

在这里插入图片描述
  注:由于工作内存与主内存的延迟同步会导致可见性问题,也就是线程A对变量的写,线程B不能马上读到;由于指令重排序会导致没有依赖关系的代码可能会由于编译器优化、处理器优化而打乱顺序执行。

2.4 happens-before规则

  先行发生的原则是判断多个线程间是否存在竞争、是否安全的依据:
  A、程序次序规则:在一个线程中,程序执行结果是有序的,但由于指令重排,从另一个线程看执行顺序可能是乱序的
  B、管程锁定规则:一个变量同一时刻只能被一个线程锁定,也就是每个lock必须要发生于上一个unlock之后
  C、volatile变量规则:在时间顺序上来说,对volatile变量的写操作先发生于后面对这个变量的读操作
  D、线程启动规则:线程的其他操作都滞后于start()操作,也就是先启动了线程,才有线程的后续一切操作
  E、线程终止规则:线程的所有操作都发生在线程终止操作之前,可通过Thread.join()、Thread.isAlive()返回值等检测线程是否终止
  F、线程中断规则:当Thread.interrupted()方法检测到线程被中断,线程肯定已经执行了interrupt()方法
  G、对象终结规则:当一个对象开始调用finalized()方法,那调这个方法之前肯定已经完成了对象初始化(也就是构造函数已经执行结束)
  H、传递性:如果A比B先发生,B比C先发生,可以得出A肯定比C先发生

  注:程序调用时间先后顺序与先行发生规则关系不大,判断是否有并发安全问题不要受时间顺序影响,要以先行发生规则为主。

2.5 as-if-serial规则

  在单线程的环境中,不管指令如何重排,最终结果都不会改变。居于这个原则,编译器、处理在处理上下文没有依赖关系的逻辑时可以对指令进行重排序以期达到更高的性能



本篇内容主要是居于《深入理解java虚拟机》关于java内存模型模块的学习笔记

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值