java内存模型

 

1. JMM内存模型作用

描述线程本地内存和主内存之间的抽象关系。线程A和线程B之间通讯,需要通过主内存

2.happens-before简介

在JMM中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须要存在happens-before关系。这里提到的两个操作既可以是在一个线程之内,也可以是在不同线程之间。
与程序员密切相关的happens-before规则如下。
  • 程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作。
  • 监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁。
  • volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读。
  • final域规则
·传递性:如果A happens-before B,且B happens-before C,那么A happens-before C。
注意 happens-before是给程序员展示的内存强约束试图,JMM如果判断重排序不会改变程序执行结果,JMM是允许编译器进行重排序的
 

3.什么是数据依赖性

编译器和处理器在重排序时,会遵守数据依赖性,编译器和处理器不会改变存在数据依赖关系的两个操作的执行顺序。因为只要重排序两个操作的执行顺序,程序的执行结果就会被改变。

4 as-if-serial语义

不管怎么重排序(编译器和处理器为了提高并行度),(单线程或正确同步的多线程)程序的执行结果不能被改变。编译器、runtime和处理器都必须遵守as-if-serial语义。

顺序一致性内存模型

    是一个理论参考模型,在设计的时候,处理器的内存模型和编程语言的内存模型都会以顺序一致性内存模型作为参照。
  1. 一个线程中的所有操作必须按照程序的顺序来执行。
  2. 不管程序是否同步,所有线程都只能看到一个单一的操作执行顺序。

6 final域重排序规则

通过下面两点能够保证在读一个对象的final域之前,一定会先读包含这个final域的对象的引用,jmm最小安全模型只能保证被引用变量被初始化成null 0 false。

     1)在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。
     2)初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序。
 

7 JMM的内存可见性保证 

按程序类型,Java程序的内存可见性保证可以分为下列3类。
  • 单线程程序。单线程程序不会出现内存可见性问题。编译器、runtime和处理器会共同确保单线程程序的执行结果与该程序在顺序一致性模型中的执行结果相同。
  • 正确同步的多线程程序。正确同步的多线程程序的执行将具有顺序一致性(程序的执行结果与该程序在顺序一致性内存模型中的执行结果相同)。这是JMM关注的重点,JMM通过限制编译器和处理器的重排序来为程序员提供内存可见性保证。
  • 未同步/未正确同步的多线程程序。JMM为它们提供了最小安全性保障:线程执行时读取到的值,要么是之前某个线程写入的值,要么是默认值(0、null、false)。

8 双重检查锁定依然不安全

memory = allocate();  // 1:分配对象的内存空间
ctorInstance(memory);  // 2:初始化对象
instance = memory;   // 3:设置instance指向刚分配的内存地址
以上步骤2和3可能被重排序,改进方法是在instance变量前加volatile关键字,禁止步骤2和3重排序.(这里利用volatile的防止重排序作用,而不是可见性)
也可以基于类初始化的解决方案。JVM在类的初始化阶段(即在Class被加载后,且被线程使用之前),会执行类的初始化。在执行类的初始化期间,JVM会去获取一个锁(类对象,InstanceHolder.class)。这个锁可以同步多个线程对同一个类的初始化。

原文地址:https://www.cnblogs.com/wzj4858/p/8214017.html