Chapter12_Java内存模型与线程

 2019-12-10 11:16  阅读(896)
文章分类:Java Core

12.2 物理机的硬件效率与一致性

  • 高速Cache层
  • 乱序执行(Out-of-Order Execution)

由于计算机的存储设备与CPU的运算速度有几个数量级的差距,所以现代计算机系统不得不加入一层读写速度尽可能接近CPU运算速度的高速Cache层;虽然它很好地解决了CPU与内存之间的速度矛盾,但也为计算机系统带来更高的复杂度:

Point: 缓存一致性问题[Cache Coherence]

当多个处理器的运算任务都涉及同一块主内存[Main Memory]区域时,将可能导致各自的缓存数据不一致,那么,当同步回到主内存时,该以谁的缓存数据为准呢?

为了解决一致性的问题,需要各个处理器访问缓存时都遵循一些协议,在读写时要根据协议来进行操作;下文的内存模型,可以理解为在特定的操作协议下,对特定的内存或高速缓存进行读写访问的过程抽象。

这类协议有MSI,MESI,MOSI,Synapse,Firefly及Dragon Protool等。

2019121000165\_1.png

乱序执行?

为了使得处理器内部的运算单元能尽量被充分利用,处理器有可能会对输入代码进行乱序执行优化,并在计算之后将乱序执行的结果重组,保证该结果与顺序执行的结果是一致的;

由此带来的问题是,
Point: 代码的执行顺序并不能靠代码的先后顺序来保证

12.3 Java内存模型

上述描述针对现实物理机,与此对应的是JVM也有类似措施,

  • 主内存与工作内存
  • 指令重排序(Instruction Reorder)

JVM规范中试图定义一种Java内存模型[Java Memory Model, JMM]来屏蔽各种硬件和操作系统的内存访问差异,以实现Java程序在各种平台下都能达到一致的内存访问效果。

定义JMM并非易事,

  • 这个模型必须定义得足够严谨,才能让Java的并发内存访问操作不会产生歧义。
  • 但也必须足够宽松,才能使得JVM的实现有足够的自由空间去利用硬件的各种特性来获取更好的执行速度。

12.3.1 主内存与工作内存

JMM的主要目标是定义程序中各个变量的访问规则,即存储及读取内存这样的底层细节。

此处的变量与Java编程中所说的变量略有区别,P319

JMM规定了所有的变量都存储在主内存(Main Memory)中,每条线程还有自己的工作内存(Working Memory)

  1. 线程的WM中保存的是被该线程使用到的变量的主内存副本拷贝,
  2. 线程对变量的所有操作(读取,赋值等)都必须在WM中进行,
  3. 线程不能直接读写MM中的变量,
  4. 不同线程间不允许互相访问对方的WM。

Point:线程间变量值的传递均需要通过MM来完成

2019121000165\_2.png

MM和WM在硬件内存中的划分与Java内存区域中的堆,栈,方法区等划分没有直接联系,从书中观点来看,MM和WM的划分依据的核心是性能。P320

12.3.2 内存间交互操作

关于MM和WM之间具体的交互协议,即

  • ?一个变量如何从MM拷贝到WM
  • ?一个变量又如何从WM同步回MM

这一类实现细节,JMM定义了以下八种原子性(下文将解释)操作

lock, unlock, read, load, use, assign, store, write
(定义参考P321)

根据后文内容,这八个原子性操作的定义暂不需重点关注,只需要知道

Point:JVM是根据这8个原子性操作来保证诸如volatile和Synchronized等同步机制

12.3.3 对于volatile型变量的特殊规则

volatile,翻译不稳定的,反复无常的;
如果用通俗的语言来说,C语言中的volatile变量的目的与Java中也类似,就是告诉编译器:Point:请不要做任何优化及多余操作,每一次都老老实实地去主内存读这个变量的数据;

为了达到这个目的,JMM对volatile专门定义了一些特殊的访问规则,并且十分拗口。
《深入理解JVM》书中对volatile的特殊规则做了相应描述,在P327。

在此记录总结volatile的通俗语义,当一个变量被定义成volatile之后,它将具备两种特性:

  1. 第一是保证此变量对所有线程的可见性为立即可知。
  2. 第二就是禁止编译器对这个变量做指令重排序优化。

关于对立即可知的误解:

个人表示这个概念误导了我很久,实际上这个”立即可知”如果翻译成”读指令立即可知”的话,会更有逻辑性;书中P324的代码示例,也解释了这个概念会导致的误解;

在《Java并发编程实战》中,对内存的操作分为3个原子性操作:

读取–增减操作–回写;

而volatile的立即可知,更加针对于上述读取步骤,就是说每次读取都可以读取到所有线程中最新回写的变量值。(针对volatile变量,线程在工作内存中use这个变量的值前,都要先去主内存load这个变量,JMM规定load与`use必须一起连续出现)。

但是,

  • 一旦读取完毕,本线程在执行下一个操作增减操作前,有可能被系统打断并转而执行别的线程,
  • 一旦别的线程此时对该变量做了修改并写回主内存后,
  • 下一次轮到本线程执行时,本线程内部操作栈顶的值,就已经是失效值了;

因此,Point:volatile是不保证内存操作的原子性的;

这里留下一个问题,是否JMM定义的8个原子性操作之间,也有个能被系统打断呢?

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

允许虚拟机实现选择可以不保证64位数据类型的load, store, read, write这四个操作的原子性;

但目前各平台下的商用虚拟机几乎都选择把64位数据的读写操作作为原子操作来对待;

点赞(0)
版权归原创作者所有,任何形式转载请联系作者; Java 技术驿站 >> Chapter12_Java内存模型与线程

相关推荐