Java 内存模型

2020/12/29 posted in  并发基础

解决Java并发编程中的有关可见性和有序性问题 --- Java 内存模型

Java 内存模型

Java 内存模型规范了JVM如何按需禁用缓存和编译器优化的方法;包括三个关键字volatile、synchronized、final以及Happens-Before规则

volatile

在C语言中原始定义就是禁止使用CPU缓存;对于volatile修饰的变量读写,不能使用CPU缓存,必须从内存中读取或者是写入

示例代码

// 以下代码来源于【参考1】
class VolatileExample {
  int x = 0;
  volatile boolean v = false;
  public void writer() {
    x = 42;
    v = true;
  }
  public void reader() {
    if (v == true) {
      // 这里x会是多少呢?
    }
  }
}

运行结果:Java 1.5 之前可能是 42 或者 0 ,1.5之后是 42 


实现原理

  1. Lock 前缀指令会引起处理器缓存回写到内存中
  2. 一个处理器的缓存回写内存会导致其他处理器的缓存无效


volatile 内存语义的实现

  1. 在volatile变量写操作之前插入StoreStore屏障 --- 禁止变量之前的普通写操作与变量的写操作重排序
  2. 在volatile变量写操作之后插入StoreLoad屏障 --- 禁止变量之后的可能的volatile写重排序(保守设置)
  3. 在volatile变量读操作之前插入LoadLoad屏障 --- 禁止变量之后的普通读操作与修饰的变量重排序
  4. 在volatile变量读操作之前插入LoadStore屏障 --- 禁止变量之后的可能的volatile变量读操作重排序(保守设置)


volatile使用优化

针对缓存行是64字节,并且对共享变量频繁的写在使用volatile变量时要补充64字节


Happens-Before 原则


定义

  1. 如果一个操作Happens-Before另一个操作,那么前面一个操作的结果对后一个操作可见,而且第一个操作的顺序在第二操作之前
  2. 如果重排序后执行结果不会导致Happens-Before 语义变化那么允许这种重排序


六条规则

  1. 程序的顺序性:一个线程中,按照程序顺序,前面的操作Happens-Before之后的操作
  2. volatile规则:读一个volatile变量的写操作,Happens-Before之后对这个变量的读操作
  3. 传递性:指如果 A Happens-Before B,且 B Happens-Before C,那么 A Happens-Before C。
  4. 管程中锁的规则:对一个锁的解锁,Happens-Before 随后对这个锁的加锁
  5. 线程start规则:如果线程A调用线程B的 start() 方法,那么该start方法操作Happens-Before线程B中的任意操作
  6. 线程Join规则:如果线程A调用线程B的join方法并成功返回,那么线程B中的任意操作Happens-Before于该join操作的返回


final

  1. 在构造器内对一个final域的写入,与随后把这个构造对象的引用赋值给一个引用变量,之间的操作不能重排序
    • JMM禁止将对final域的写排序到构造求之外
    • 编译器会在final写之后,构造函数return之前插入StoreStore屏障
  2. 初次读一个包含final域的对象的引用,与随后读这个final域不能重排序