Java虚拟机--内存模型

缓存一致性:

“让计算机并发处理多个任务”和“更充分利用计算机处理器的效能”之间看起来是因果关系,但实现起来非常麻烦。因为绝大多数运算任务都需要与内存交互,并非纯粹的计算。由于处理器和内存的处理速度不匹配(处理器运算速度远大于从内存中读取数据的速度),所以现代计算机系统通常加入一层高速缓存(Cache)来作为内存和处理器之间的缓冲:将运算需要的数据复制到Cache中,让运算能快速进行;运算结束后再从缓存同步到内存中。这样处理器可以不用等待缓慢的内存读写。

这种方法解决了处理器和内存之间的速度问题,但引入了一个更复杂的问题:缓存一致性。在多处理器系统中每个处理器都有自己的高速缓存(Cache),而他们又共享同一个主内存,很容易想到,不同处理器对主内存的数据进行缓存和回写会带来数据的不一致问题。这种情况下要引入一些协议来解决这个问题。

乱序执行:

为了使处理器内部运算单元能尽量被充分运用,处理器会对代码进行乱序执行优化,然后在计算后将结果重组,保证该结果与顺序执行的结果一致,但不保证各语句的先后执行顺序与输入时的顺序一致。Java虚拟机的即时编译器中也有类似的指令重排序优化。

Java内存模型:

内存模型可以理解为:在特定操作协议下,对特定的内存或缓存进行读写访问的过程抽象。

Java内存模型的主要目标是定义程序中的各个变量的访问规则,即在虚拟机中将变量存储在内存和从内存中读取变量这样的底层细节。注意:这里的变量和Java编程中的变量意义不同,它包括实例字段、静态字段和构成数组对象的元素,但不包括局部变量和方法参数,因为后者是线程私有的,不会被共享,也就不存在竞争问题。

Java内存模型规定了所有变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存保存了该线程所需要用到的变量的副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存的变量。线程间的变量访问和传递不可直接进行,必须要通过主内存来完成。

可以将下面这张图和缓存一致性中的图对比理解:

内存间的交互操作:

关于主内存和工作内存之间的交互协议,即读取和回写的实现细节,Java内存模型定义了8种操作来完成,这些操作含有原子性:

  1. lock:主内存操作,锁定变量,标识其为线程独占的状态。
  2. unlock:主内存操作,解锁变量,将其从线程独占的状态中释放出来。
  3. read:主内存操作,读取变量到工作内存。
  4. load:工作内存操作,将读取到的变量赋值给工作内存中的变量副本。
  5. use:工作内存操作,将变量值传递给执行引擎以供操作。
  6. assign:工作内存操作,将执行引擎操作后的值赋给工作内存中的变量。
  7. store:工作内存操作,将工作内存中的变量传递给主内存。
  8. write:主内存变量,将store得到的值写入主内存中的变量。

如果把一个变量从主内存复制到工作内存,必须顺序执行read和load操作;反之将一个变量从工作内存回写到主内存,store和write也要顺序执行。但虽然要顺序执行不代表到连续执行,也就是说两条语句之间可以插入其他执令。

Java内存模型还规定了执行上述8钟基本操作必须满足的规则:

  1. read和load,store和write必须成对出现,即工作内存或主内存必须将已经从另一方读取到的值写入自己所持有的变量,不允许拒绝。
  2. 工作内存最后一次assign必须同步回主内存,而工作内存同步回主内存的变量也必须执行过assign操作。
  3. 工作内存不可以使用未初始化的变量,即对一个变量实施use,store操作之前必须要有load和assign操作。
  4. 一个变量只能由一个线程lock,也只能由这个线程unlock,线程可以多次lock,对应的解锁需要同样次数的unlock。
  5. 不允许unlock未被lock过的变量。
  6. unlock操作必须在store和write之后。

对volatile型变量的特殊规则:

关键字volatile可以说是Java虚拟机提供的最轻量级的同步机制。当一个变量被定义为volatile之后,他将具备两种特性:

  • 第一,保证此变量对所有线程的可见性;
  • 第二,禁止指令重排序优化;

下面分别讨论这两个特性:

保证变量对所有线程的可见性:

这里的“可见性”是指当一条线程修改了这个变量的值,新值对其他变量来说是可以立即得知的,而普通变量做不到这一点。普通变量的值均需要通过主内存来完成,例如线程A修改了一个变量的值,然后向主内存回写,线程B在线程A回写完之后再从主内存中读取值,新变量的值才对线程B可见。

但要注意,volatile变量在各个线程的工作内存中不存在一致性问题,但Java里面的运算并非原子操作,导致volatile变量的运算在并发情况下一样是不安全的。

例如:定义一个volatile类型的变量count,有一个方法是简单地count++。如果发起20个线程每个线程调用1w次自增方法,最后count结果是小于20w。 因为虽然每个线程取count的时候能够取到当前的正确的值,但由于++操作不是原子性的,在这个线程进行加操作的时候,其他线程可能已经把count的值加大了,而这个线程操作栈顶的值已经成了过期数据,然后回写地时候就可能把较小的值回写到主内存中。

由于volatile变量只能保证可见性,在不符合以下两条规则的运算场景下,仍然需要通过加锁(使用synchronized或java.util.concurrent中的原子类)来保证原子性:

  1. 运算结果并不依赖变量的当前值,或者能保证只有单一线程修改变量的值;
  2. 变量不需要与其他状态变量共同参与不变约束。

禁止指令重排优化:

分析下面的代码:

volatile boolean initizlized = false;

//下面代码在线程A中运行
configOption = new HashMap();
...
initizlized = true;

//下面代码在线程B中运行
while(!initizlized){
    sleep();
}

如果定义initialized的时候没有使用volatile修饰,就有可能由于指令重排序优化,导致位于A线程的最后语句代码“initialized=true”被提前执行(所指的指令重排序是机器级的优化操作,提前执行是指这条语句对应的汇编语言提前执行),这样会让B线程提前执行相关操作,很容易出问题,而使用volatile则可以避免这种情况的发生。

通过这8个基本操作和上述规定,再加上volatile特殊规则,可以确定Java程序中哪些内存访问操作在并发的情况下是安全的。但根据上述严谨的定义去判断,实践起来比较麻烦,所以通常可以根据一个和定义等效的判断原则----先行发生原则,来判断一个访问在并发情况下是否安全。

下一篇:Java虚拟机--先行发生原则

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏烙馅饼喽的技术分享

用ECMAScript4 ( ActionScript3) 实现Unity的热更新 -- CustomYieldInstruction 自定义中断指令

ActionScript3脚本引擎为了方便热更新逻辑开发,提供的从脚本继承Unity类库功能在一些情况下可以提供开发的便利。 这次来建立一个示例,演示一下如何在...

3589
来自专栏JackieZheng

AngularJS in Action读书笔记3——走近Services

  试着想想这些问题:如果一个controller只关心自己所控制的view页面,那么对于整个application来说,你如何调用想要的function;如果...

2119
来自专栏灯塔大数据

每周学点大数据 | No.61磁盘算法实践(下)

NO.61 磁盘算法实践(下) Mr. 王:嗯,这是一个应用非常广泛的数据结构,跟你讲讲它的原理吧。Hash 表又叫散列表,是一种非常常见的用于实现数据字典的...

3016
来自专栏cloudskyme

什么是线程安全

什么是线程安全?       如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且...

3298
来自专栏C/C++基础

内存池介绍与经典内存池的实现

利用默认的内存管理函数new/delete或malloc/free在堆上分配和释放内存会有一些额外的开销。

871
来自专栏TechBox

【iOS】运行时消息传递与转发机制前言(一)对象的消息传递机制 objc_msgSend()(二)消息转发流程参考文章

1334
来自专栏java思维导图

值得收藏!Redis五大数据类型应用场景(一)

Redis开创了一种新的数据存储思路,使用Redis,我们不用在面对功能单调的数据库时,把精力放在如何把大象放进冰箱这样的问题上,而是利用Redis灵活多变的数...

2604
来自专栏Java架构沉思录

一文理清Java内存区域

Java虚拟机在执行 Java 程序的过程中会把它管理的内存划分为若干个不同的数据区域。根据《Java 虚拟机规范》将 Java虚拟机所管理的内存分为以下几个运...

1162
来自专栏Java架构沉思录

你真的懂volatile关键字吗

volatile这个关键字可能很多朋友都听说过,或许也都用过。在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结...

1211
来自专栏Python

nginx配置 location及rewrite规则详解

1702

扫码关注云+社区

领取腾讯云代金券