Java 内存模型

2017-12-27  本文已影响0人  panning

目录

一、Java 内存模型的主要目标
二、主内存和工作内存
三、内存件的交互操作
四、对于 volatile 型变量的特殊规则
五、对于 long 和 double 型变量的特殊规则
六、原子性、可见性与有序性
七、先行发生原则

一、Java 内存模型的主要目标

Java 内存模型的主要目标是 定义程序中各个变量的访问规则,即在虚拟机中 将变量存储到内存从内存中取出变量 这样的底层细节。此处的变量(Variables)与 Java 变成中所说的变量有所区别,它包括了实例变量、静态变量和构成数组对象的元素,但不包括局部变量与方法参数,因为后者是线程私有的,不会被共享,自然就不会存在竞争问题。

为了获得较好的执行效能,Java 内存模型并没有限制执行引擎使用处理器的特定寄存器或缓存来和主内存进行交互,也没有限制即时编译器进行调整代码执行顺序这类优化措施。

二、主内存与工作内存

Java 内存模型规定了所有的变量都存储在主内存(Main Memory, 这里指的是虚拟机内存的一部分)中。每条线程还有自己的工作内存(Working Memory,可以与处理器高速缓存类比),线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量。

不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成,线程、主内存、工作内存三者的交互关系如下图所示。 线程、主内存、工作内存三者的交互关系

这里说的主内存、工作内存和 Java 内存区域中的 Java 堆、栈、方法区等并不是同一个层次的内存划分,这两者基本是没有关系的,如果两者一定要勉强对应起来的话,那从变量、主内存、工作内存的定义来看,主内存主要对应于 Java 堆中的对象实例数据部分,而工作内存则对应于 Java 虚拟机栈中的部分区域。

三、内存件交互操作

关于 主内存 和 工作内存 之间具体的交互协议,即一个变量如何从主内存拷贝到工作内存、如何从工作内存同步回主内存之类的实现细节,Java 内存模型中定义了一下 8 中操作来完成,虚拟机实现时必须保证下面提及的每一种操作都是原子的、不可再分的(对于 double 和 long 类型的变量来说,在某些平台上允许有例外,这个问题将在下文中说明)。

其中 read、load、use、assign、store 和 write 这 6 种操作的关系如下图所示。 点击看大图

Java 内存模型规定了在执行上述 8 中基本操作时必须满足如下规则:

四、对于 volatile 型变量的特殊规则

当一个变量定义为 volatile 之后,它将具备两种特性:保证变量的可见性禁止指令重排序优化

了解了volatile 的语义问题,那么我们来比较一下 使用 volatile 的代码和使用其他的同步工具更快哪个更快?

最后看一下 Java 内存模型对 volatile 变量定义的特殊规则。假定 T 表示一个线程, V 和 W 分别表示两个 volatile 型变量,那么在进行 read、load、use、assign、store 和 write 操作时需要满足如下规则:

五、对于 long 和 double 型变量的特殊规则

Java 内存模型要求 lock、unlock、read、load、use、assign、store、write 这 8 个操作都具有原子性,但是对于 64 位的数据类型(long 和 double),在模型中特别定义了一条相对宽松的规定:允许虚拟机将没有被 volatile 修饰的 64 位数据的读写操作划分为两次 32 位的操作来进行,即允许虚拟机实现选择可以不保证 64 位数据类型的 load、store、read 和 write 这 4 个操作的原子性。

不过,在实际开发中,目前各种平台下的商用虚拟机几乎都选择把 64 位的数据的读写操作作为院子操作来对待,因此我们在编写代码时一般不需要把用到的 long 和 double 变量专门声明为 volatile。

六、原子性、可见性与有序性

Java 内存模型是围绕着在并发过程中如何处理原子性、可见性和有序性这 3 个特征来建立的。

介绍完并发中 3 中重要的特性后,有没有发现 synchronized 关键字在需要 3 中特性的时候都可以作为其中一种的解决方案。

七、先行发生原则

如果 Java 内存模型中所有的有序性都仅仅靠 volatile 和 synchronized 来完成,那么有一些操作将会变得很烦琐,但是我们在编写 Java 并发代码的时候并没有感觉到这一点,这是因为 Java 语言中有一个 “先行发生”(happens-before)的原则。这个原则非常重要,它是判断数据是否存在竞争、线程是否安全的主要依据,依靠这个原则,我们可以通过几条规则一揽子地解决并发环境下两个操作之间是否可能存在冲突的所有问题。

下面是 Java 内存模型下一些“天然的”先行发生关系,这些先行发生关系无须任何同步器协助就已经存在,可以在编码 中直接使用。如果两个操作之间的关系不在此列,并且无法从下列规则推导出来的话,它们就没有顺序性保障,虚拟机可以对它们随意的进行重排序。

上一篇下一篇

猜你喜欢

热点阅读