关于主内存与工作内存之间具体的交互协议,即一个变量如何从主内存拷贝到工作内存,如何从工作内存同步回主内存之类的实现细节,java内存模型中定义了8种操作来完成,虚拟机实现时必须保证这8种操作都是原子的、不可分割的(对于long和double类型的变量来说,load、store、read跟write在某些平台上允许例外)。
8种基本操作:
  1. lock,锁定,所用于主内存变量,它把一个变量标识为一条线程独占的状态。
  2. unlock,解锁,解锁后的变量才能被其他线程锁定。
  3. read,读取,所用于主内存变量,它把一个主内存变量的值,读取到工作内存中。
  4. load,载入,所用于工作内存变量,它把read读取的值,放到工作内存的变量副本中。
  5. use,使用,作用于工作内存变量,它把工作内存变量的值传递给执行引擎,当JVM遇到一个变量读取指令就会执行这个操作。
  6. assign,赋值,作用于工作内存变量,它把一个从执行引擎接收到的值赋值给工作内存变量。
  7. store,存储,作用域工作内存变量,它把工作内存变量值传送到主内存中。
  8. write,写入,作用于主内存变量,它把store从工作内存中得到的变量值写入到主内存变量中
8种操作的规则:
  java内存模型还规定了在执行上述8种基本操作时必须满足如下规则:
  1. 不允许read和load、store和write操作之一单独出现,即不允许加载或同步工作到一半。
  2. 不允许一个线程丢弃它最近的assign操作,即变量在工作内存中改变了之后,必须吧改变化同步回主内存。
  3. 不允许一个线程无原因地(无assign操作)把数据从工作内存同步到主内存中。
  4. 一个新的变量只能在主内存中诞生。
  5. 一个变量在同一时刻只允许一条线程对其进行lock操作,但lock操作可以被同一条线程重复执行多次,,多次lock之后必须要执行相同次数的unlock操作,变量才会解锁。
  6. 如果对一个对象进行lock操作,那会清空工作内存变量中的值,在执行引擎使用这个变量前,需要重新执行load或assign操作初始化变量的值。
  7. 如果一个变量事先没有被lock,就不允许对它进行unlock操作,也不允许去unlock一个被其他线程锁住的变量。
  8. 对一个变量执行unlock操作之前,必须将此变量同步回主内存中(执行store、write)。
  有如上8种内存访问操作以及规则限定,再加上对volatile的一些特殊规定,就已经完全确定了java程序中哪些内存访问操作是在并发下安全的。
对于volatile的特殊规则:
  volatile有两个特性:1、对所有线程可见;2、防止指令重排;我们接下来说明一下这两个特性。
  可见性,是指当一条线程修改了某个volatile变量的值,新值对于其它线程来说是可以立即知道的。而普通变量无法做到这点。但这里有个误区,由于volatile对所有线程立即可见,对volatile的写操作会立即反应到其它线程,因此基于volatile的变量的运算在并发下是安全的。这是错误的,原因是volatile所谓的其它线程立即知道,是其它线程在使用的时候会读读内存然后load到自己工作内存,如果这时候其它线程进行了修改,本线程的volatile变量状态会被置为无效,会重新读取,但如果本线程的变量已经被读入执行栈帧,那么是不会重新读取的;那么两个线程都把本地工作内存内容写入主存的时候就会发生覆盖问题,导致并发错误。
  防止指令重排,重排序优化是机器级的操作,也就是硬件级别的操作。重排序会打乱代码顺序执行,但会保证在执行过程中所有依赖赋值结果的地方都能获取到正确的结果,因此在一个线程的方法执行过程中无法感知到重排的操作影响,这也是“线程内表现为串行”的由来。volatile的屏蔽重排序在jdk1.5后才被修复。原理是volatile生成的汇编代码多了一条带lock前缀的空操作的命令,而根据IA32手册规定,这个lock前缀会使得本cpu的缓存写入内存,而写入动作也会引起别的cpu或者别的内核无效化,这相当于对cpu缓存中的变量做了一次store跟write的操作,所以通过这样一个操作,可以让变量对其它cpu立即可见(因为状态被置为无效,用的话必须重新读取)。
  另外,java内存模型对volatile变量有三条特殊规则:
  a、每次使用变量之前都必须先从主内存刷新最新的值,用于保证能看见其它线程对变量的修改;
  b、每次对变量修改后都必须立刻同步到主内存中,用于保证其它线程可以看到自己的修改;
  c、两个变量都是volatile的,将数据同步到内存的时候,先读的先写;
long跟double变量的特殊规则
  对于64位的数据类型long跟double,java内存模型定义了一条相对宽泛的规定:允许虚拟机将没有被volatile修饰的64位数据操作分为两次32位的操作来进行。也就是允许虚拟机不保证64位数据load、store、read跟write这4个操作的原子性,这就是long跟double的非原子性协定。如果真的这样,当多个线程共享一个并未声明为volatile的long或者double类型的变量,并同时对他们进行读取修改,那么某些线程可能会读到一些既非初始值也不是其他线程修改值的代表了“半个变量”的数据。
  不过这种读到“半个变量”的情况非常罕见,因为java内存模型虽然允许实现为非原子的但“强烈建议”将其实现为原子操作,实际开发中,所有商用虚拟机都将其实现为原子操作,因此,这点我们并不需要担心。

2、java内存间交互操作的更多相关文章

  1. 转: 【Java并发编程】之十七:深入Java内存模型—内存操作规则总结

    转载请注明出处:http://blog.csdn.net/ns_code/article/details/17377197 主内存与工作内存 Java内存模型的主要目标是定义程序中各个变量的访问规则, ...

  2. 【Java并发编程】:深入Java内存模型—内存操作规则总结

    主内存与工作内存 java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节.此处的变量主要是指共享变量,存在竞争问题的变量.Java内存模 ...

  3. Java并发编程--7.Java内存操作总结

    主内存和工作内存 工作规则 Java内存模型, 定义变量的访问规则, 即将共享变量存储到内存和取出内存的底层细节  所有的变量都存储在主内存中,每条线程有自己的工作内存,工作内存中用到的变量, 是从主 ...

  4. Java内存模型

    1. 概述 多任务和高并发是衡量一台计算机处理器的能力重要指标之一.一般衡量一个服务器性能的高低好坏,使用每秒事务处理数(Transactions Per Second,TPS)这个指标比较能说明问题 ...

  5. Jvm基础(2)-Java内存模型

    Jvm基础(2)-Java内存模型 主内存和工作内存 Java内存模型包括主内存和工作内存两个部分:主内存用来存储线程之间的共享变量:而工作内存中存储每个线程的相关变量. 如下图所示: 需要注意的是: ...

  6. java内存模型分析2

    不同线程之间无法直接访问对方工作内存中的变量,线程间变量值的传递均需要在主内存来完成,线程.主内存和工作内存的交互关系如下图所示,和上图很类似. 这里的主内存.工作内存与Java内存区域的Java堆. ...

  7. Java内存模型(转载)

    1. 概述 多任务和高并发是衡量一台计算机处理器的能力重要指标之一.一般衡量一个服务器性能的高低好坏,使用每秒事务处理数(Transactions Per Second,TPS)这个指标比较能说明问题 ...

  8. 第十二章 Java内存模型与线程

    Java内存模型(Java Memory Model,JMM): 主内存与工作内存:Java内存模型主要是定义程序中各个变量的访问规则.Java内存模型规定了所有的变量都存储在主内存(Main Mem ...

  9. Java 多线程(六)之Java内存模型

    目录 1. 并发编程的两个问题 2 CPU 缓存模型 2.1 CPU 和 主存 2.2 CPU Cache 2.3 CPU如何通过 Cache 与 主内存交互 2.4 CPU 缓存一致性问题 3 Ja ...

随机推荐

  1. 解决Eclipse+ADT连接夜神模拟器失败问题

    问题1: 运行夜神模拟器,cmd执行 adb devices不显示 答案1: 原因可能是夜神模拟器的adb版本与sdk下的adb版本不一致,拷贝sdk下的adb.exe并改名为nox_adb.exe替 ...

  2. Backbone.js 0.9.2 中文解释

    // Backbone.js 0.9.2 // (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc. // Backbone may be freely ...

  3. webrequest、httpwebrequest、webclient、HttpClient 四个类的区别

    一.在 framework 开发环境下: webrequest.httpwebreques  都是基于Windows Api 进行包装, webclient 是基于webrequest 进行包装:(经 ...

  4. linux学习之路(4)

    用户身份与文件权限 通过uid来区分:  管理员 UID 为 0:系统的管理员用户. 系统用户 UID 为 1-999: Linux 系统为了避免因某个服务程序出现漏洞而被黑客提 权至整台服务器,默认 ...

  5. angular 服务之间依赖注入

    import { Injectable } from '@angular/core'; @Injectable() export class LoggerServiceService { constr ...

  6. arp欺骗进行流量截获-2

    上一篇讲了原理,那么这一篇主要讲如何实现.基本上也就是实现上面的两个步骤,这里基于gopacket实现,我会带着大家一步步详细把每个步骤都讲到. ARP 欺骗 首先就是伪造ARP请求,让A和B把数据包 ...

  7. 《C#多线程编程实战》2.9 ReaderWirterLockSlim

    可以多线程进行读写操作. 比如书上的示例代码是三个线程进行读取,两个线程进行写入工作. 如果 用之前学过的也不是不可以用,但是用的有些多. 所有ReaderWirterLockSlim专门为此而来. ...

  8. SpringBoot+MyBatis+MySQL读写分离(实例)

    ​ 1. 引言 读写分离要做的事情就是对于一条SQL该选择哪个数据库去执行,至于谁来做选择数据库这件事儿,无非两个,要么中间件帮我们做,要么程序自己做.因此,一般来讲,读写分离有两种实现方式.第一种是 ...

  9. python 面向对象十一 super函数

    python 面向对象十一 super函数   super函数用来解决钻石继承. 一.python的继承以及调用父类成员 父类: class Base(object): def __init__(se ...

  10. 【guava】对象处理

    一,equals方法 我们在开发中经常会需要比较两个对象是否相等,这时候我们需要考虑比较的两个对象是否为null,然后再调用equals方法来比较是否相等,google guava库的com.goog ...