第3章| Java内存模型——01基础

Java线程之间的通信对开发人员完全透明,内存可见性就是经常困扰的问题之一。本章可分为4个部分:

  1. Java内存模型的基础
    主要介绍内存模型相关的基本概念;
  2. Java内存模型的顺序一致性
    主要介绍重排序与顺序一致性内存模型;
  3. 同步原语
    主要介绍3个同步原语(synchronized、volatile和final)的内存语义及重排序规则在处理器中的实现;
  4. Java内存模型的设计
    主要介绍Java内存模型的设计原理,及其与处理器内存模型和顺序一致性内存模型的关系。

Java内存模型的基础

问题1:线程之间是如何通信的?(通信指线程之间通过一种机制来交换信息)

①共享内存:在共享内存的并发模型里,线程之间共享程序的公共状态,通过写-读内存中的公共状态进行隐式通信;
②消息传递:在消息传递的并发模型里,线程之间没有公共状态,线程之间必须通过发送消息来显式通信。

问题2:线程之间是如何同步的?(同步指程序中用于控制不同线程间操作发生相对顺序的机制)

①在共享内存并发模型里,同步是显式进行的。开发人员必须显式指定某个方法或某段代码需要在线程之间互斥执行;
②在消息传递并发模型里,由于消息的发送必须在消息的接收之前,因此同步是隐式进行的。

Java的并发采用的是共享内存模型,因此其通信是隐式的,同步是显式的。

Java内存模型的抽象结构

  1. Java中所有实例域、静态域和数组元素都存储在堆内存中,堆内存在线程之间共享;
  2. Java中局部变量、方法定义参数和异常处理器参数不会在线程之间共享,因此不存在内存可见性问题,也不会受到内存模型的影响。

Java线程间的通信由Java内存模型(JMM)控制,其决定一个线程对共享变量的写入何时对另一个线程可见。
Java内存模型抽象结构示意图如下:

Java内存模型抽象结构示意图

JMM通过控制主内存与每个线程本地内存之间的交互,来为Java开发人员提供内存可见性保证

从源代码到指令的重排序

为了提高程序执行性能,编译器和处理器通常会重排序指令,共有以下3种重排序

  1. 编译器优化的重排序
    编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。

  2. 指令级并行的重排序
    现代处理器采用了指令级并行技术(Instruction-Level Paralleism, ILP)将多条指令重叠执行。在不存在数据依赖性下,处理器可改变语句对应机器指令的执行顺序。

  3. 内存重排序
    由于处理器的缓存和读/写缓冲区的存在,使得加载和存储操作看上去是乱序执行。

从源码到最终执行的指令序列的示意图

J内存模型的处理器重排序规则会要求Java编译器在生成指令时,插入特定类型的内存屏障指令,来禁止特定类型的处理器重排序,从而避免多线程可能出现内存可见性的问题

写缓冲区,用来临时保存写入内存的数据,有以下好处

  1. 可以保证指令流水线持续运行
  2. 可以避免由于处理器速度过快从而需要等待向内存写入造成的时间延迟
  3. 批处理刷新写缓冲区,与合并写缓冲区中对同一内存地址的多次写的方式减少了对内存总线的占用

缺陷:每个写缓冲区仅对自己的处理器可见,这样会导致处理器对内存的读/写操作不一定与实际发生的内存读/写顺序一致。

一般常见的处理器都允许Store-Load重排序,不允许对存在数据依赖的操作做重排序。常用的Intel和AMD所使用的x86架构,只支持Store-Load重排序,这是因为CPU在写入的时候是首先写入寄存器,包括各级缓存,此时并没有刷新到内存中,如果等待其完成再读则太慢了。

happens-before

从jdk5后,Java的内存模型为JSR-133内存模型。该内存模型使用happens-before来描述操作之间的内存可见性,这里的操作可以使同一个线程,也可以是不同线程。
happens-before规则:

  • 程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作;
  • 监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁加锁;
  • volatile变量规则:对于一个volatile域的写,happens-before于任意后续对这个volatile域的读;
  • 传递性:如果A happens-before B,且B happens-before C,那么A happens-before C
  • start规则:如果线程A执行操作ThreadB.start()(启动B线程),那么A线程的ThreadB.start()操作happens-before于线程B的任意操作;
  • join()原则:如果线程A执行操作ThreadB.join()并返回成功,那么线程B中的任意操作happens-before与线程A从ThreadB.join()操作成功返回。

Note:两个操作之间由happens-before关系,并不意味着前一个操作必须要在后一个操作之前执行(程序顺序规则:如果二者不存在数据依赖,就可以颠倒下顺序),其只要求前一个操作(执行的结果)对后一个操作可见,且前一个操作按顺序排在后一个操作之前。
happens-before与JMM的关系

码哥 wechat
欢迎关注个人订阅号:「码上行动GO」