您的位置:首页 > 编程语言 > Java开发

JVM读书笔记之Java内存模型与线程

2017-10-18 11:26 127 查看
      这次我们讨论下JVM与JMM(Java内存模型)之间的联系与区别。

一. 硬件的效率与一致性
      所有的运算任务不能只靠处理器的计算完成,其中还涉及到读取运算数据,内存的交互,存储运算结果等,不能仅靠寄存器来解决,由于计算机系统的存储设备与处理器运算速度之间有几个数量级的差距,所以都会加入一层读写速度尽可能接近处理器运算速度的高速缓存(Cache)作为内存和处理器之间的缓存(将运算需要的数据复制到缓存中,让运算快速进行,运算结束后再从缓存同步到内存)。   
         当多个处理器的计算任务涉及到同一块主内存区域,为了解决缓存数据不一致性问题,就需要缓存一致性协议,比如MSI,MESI,MOSI等。此外,为了使处理器内部单元被充分使用,还会对输入代码进行乱序执行(Out-Of-Order Execution),Java虚拟机的JNI(即时编译器)也有类似的指令重排优化。

二.  JVM与JMM的关系
     

三.  Java内存模型(类比计算机模型)
       Java虚拟机规范试图定义一种JMM来屏蔽各种硬件与操作系统的内存访问差异,实现Java程序在各个平台下达到一致的并发效果。C与C++都是直接使用物理硬件(操作系统的内存模型),因此会在不同的平台上有差异。
     1. 主内存与工作内存
        JMM规定所有变量(线程共享变量,实例变量(成员变量),静态变量,数组对象,不包括局部变量与方法参数)存储在主内存中,每个线程还有自己的工作内存(类比高速缓存),线程的工作内存保存了被该线程使用到的变量的主内存副本拷贝,线程对变量(线程共享变量)的操作必须在工作内存中进行,不能直接读写主内存变量(形参与实参),不同的线程之间也无法直接访问对方的工作内存,它们之间的变量(线程共享)传递需要主内存完成。

        注意:这里的工作内存与主内存不是Java堆与Java栈,主内存是硬件的内存,工作内存会优先存储已寄存器与高速缓存中。
    2. 内存之间的交互
       主内存与工作内存之间的交互协议,Java内存模型定义了八种操作完成:
lock:作用于主内存,把一个变量标识为一条线程独占状态
unlock:作用于主内存,把一个变量释放锁,被其他线程访问
read:作用于主内存变量,把一个变量传输到工作内存
load:作用于主内存变量,把read操作从主内存得到的变量值放入工作内存的变量副本中
Use:作用于工作内存变量,把工作呢村中的变量值传递给执行引擎。
Assign:作用于工作内存,把一个从执行引擎接收到的值赋值给工作内存变量
store:作用于工作内存,把工作内存中一个变量的值传到主内存中
write:作用于主内存,把store操作获取的变量值放入主内存变量中
   执行上述八种基本操作,必须满足下面规则:
 不允许read和load,store和write操作单独出现,即不允许变量从主内存读取但工作内存不接受或变量从工作内存写入到主内存,主内存不接受
不允许线程丢弃它最近的assign操作,即变量在工作内存中赋值后必须把该变化同步回主内存;下面对该变量的操作需要再次从主内存读取。
一个新变量只能在主内存中“诞生”,不允许工作内存使用未被初始化(load或assign)的变量。即为变量必须先定义声明,初始化才能被使用。
一个变量在同一时刻只允许一条线程对去进行lock操作,但lock可以执行多次,再执行同样次数的unlock,变量能被解锁。
对一个变量执行lock操作,将会清空工作内存中的变量的值,在执行引擎使用这个变量之前,需要重新执行load与assign操作
如果一个变量没有被lock,则不允许执行unlock操作;也不允许区unlock一个被其他线程锁定住的变量。
对一个变量执行unlcok操作之前,必须把此变量同步回主内存(store,write操作)
     3.volatile关键字规则
        volatile关键字特性:
保证线程可见性,当一条线程修改了这个变量的值,其他线程可以立即得知。但是不能保证原子性(在一个线程执行volatile变量时,其他线程改变了该变量,但是最后同步到内存的是当前变量),需要通过加锁来保证原子性。以下两种情况适合使用volatile关键字:1.运算结果不依赖变量的当前值,或者只能保证单一线程改变其值;2.变量不需要与其他状态变量参与不变约束。
禁止指令重排优化,保证执行顺序。
      JMM对volatile变量定义的特殊规则:
工作内存中,每次使用(注意是User,不是Assign,还是可能存在线程不安全可能)volatile变量,都必须先从主内存中刷新最新值,保证能看到其他线程对值的改变。      
工作内存中,每次修改volatile变量必须立刻同步回主内存中,保证其他线程可以看到
volatile变量不会被指令重排,保证代码顺序与程序顺序相同
     4. 对于long和double型变量的要求
         JMM要求八种基本操作都具有原子性,但是对于64位的数据(long,double)JMM规定可以将没有volatile修饰的变量进行读写划分为两次32位操作,即JVM可以不保证read,load,store,write原子性。但是各个平台下的商用虚拟机都会选择64位数据操作为原子操作。
     5. 原子性,可见性和有序性
         JMM是围绕着并发过程中如何处理原子性,可见性和有序性三个特征建立的。
原子性:JMM直接保证原子性变量操作包括:read,load,assign,user,store,write这六个。基本数据类型的访问读写都是具备原子性的。更大范围的原子性---synchronized关键字保证。
可见性:JMM通过变量修改后将新值同步回主内存,在变量读取之前从主内存刷新变量值这种操作保证可见性。无论是普通变量还是volatile变量都是如此,区别是volatile能保证新值立即同步到主内存,使用前立即刷新(多线程可见性的原因),普通变量不能保证。除了volatile,java的synchronized(对变量执行unlcok操作,必须先把变量同步到主内存)与final关键字也能实现可见性。
有序性:Java程序中的有序性:线程内观察,所有操作是有序的,线程内表现为串行语义;在线程外观察,所有操作都是无序的(指令重排现象;工作内存与主内存同步延迟)。Java语言通过volatile(自带禁止指令重排)与synchronized(一个变量同一时刻只允许一条线程对其加锁)关键字来保证线程之间操作的有序性。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息