JAVA 内存模型(一)

综述:

 简单的说,Java的内存模型定义了在一个线程对一个共享变量进行修改后,修改后的共享变量什么时候对其它线程可见。

作用:

  1. 对于程序员
    JMM给程序员呈现出来的是具有顺序一致性的强内存模型。(通俗点就是所见即所得)
  2. 对于处理器与编译器
    JMM给处理器与编译器提供了一个比较弱的happens-before内存模型,这样尽可能多的给处理器与编译器优化代码的空间。

处理器与编译器的优化技术

  1. 编译器优化的重排序。
    编译器在不改变单线程程序语义的前提下,可以对我们编写的代码进行重排。如下:
public class Test {
    public static void main(String args []) {
        int a = 1;       // 1
        int b = 2;      // 2
        // 在单线程语义下这个程序的输出为 1
        // 交换标注为 1,2 的两行代码不改变单线程程序语义
        // 编译器可以对这样的代码进行指令重排,以提高程序运行的性能
        System.out.println(a);
    }
 }
public class Test {
    public static void main(String args[ ]) {
        int a = 0;                          // 1
        a = 2;                              // 2
        a = 3;                              // 3
        // 在单线程语义下这个程序的输出为3
        // 如果标注为 2 ,3的代码进行交换顺序,那就改变了单线程语义,JMM
        // 是禁止这种重排序的。
        System.out.println(a);
    }
}

2 . 处理器的指令级并行重排序
现代处理器采用了指令级并行技术,来对多条指令进行重排序。如果指令间不存在数据依赖(对同一个变量写后读,读后写,写后写),那处理器就可以改变指令间的执行顺序。如下:

  0x00007f31c9108ac0: mov    %eax,-0x14000(%rsp)
  0x00007f31c9108ac7: push   %rbp
  0x00007f31c9108ac8: sub    $0x30,%rsp
  0x00007f31c9108acc: movabs $0x7f31c8c00448,%rdi  ;   {metadata(method data for {method} {0x00007f31c8c00260} 'test' '()V' in 'testTwo')}
  0x00007f31c9108ad6: mov    0xdc(%rdi),%ebx
  // 假设以上5条汇编指令之间不存在数据依赖,如果处理器的流水线大于等于5,那么,这5条汇编指令可以并行的进行计算。

3 . 内存系统的重排序
由于处理器使用缓存,这使得加载和存储操作看上去是在乱序执行。如图[1]:

intel i7 处理器是4核8线程,每个核心都有自己的L1, L2 缓存,当不同的核心缓存相同的共享变量,并写回到L3 缓存时,就存在内存系统的重排序。

Happens-before 规则

  综述:happens-before 关系是由Lamport(1978)这位大神提出的。它的表述为: a -> b 读作“a 在 b 之前发生”,意思是所有的进程(分布式系统中)/ 线(我们现在讨论的JMM)一致认为事件 a  先于事件 b 发生:注意:这里的先发生实际上是一种可见性的表述,它并不代表在物理时间上事件a先于事件b发生,而是事件b在执行前要能看到事件a执行后的结果。这就为处理器,与编译器的重排提供了保证。

我们来看看JSR-133对JMM中happens-before规则的示例:
1. 程序顺序规则:一个线程中的的每个操作,happens-before于该线程中的任意后续操作。
2. 监视器规则: 对一个锁的解锁,happens-before于随后对这个锁的加锁。
3. volatile变量规则:对一个volatile域的写,happens-before与任意后续对这个volatile域的读。
4. 传递性: 如果 a -> b, b -> c, 则 a -> c.
5. 线程启动 start() 规则:如果线程A 执行操作ThreadB.start()(启动线程B),那么A线程的启动线程的操作,happens-before于线程B中的任意操作。
6. join()规则: 如果线程A 执行ThreadB.join()并成功返回,那么线程B中的任意操作,happens-before 于线程A 从ThreadB.join()操作成功返回。

顺序一致性内存模型

综述:顺序一致性模型是一个被计算机科学家理想化了的理论参考模型,它为程序员提供了内存可见性的保证。
顺序一致性的两大特征:
1. 一个线程中所有操作必须按照程序的顺序来执行
2.不管程序是否同步,所有线程都只能看到一个单一的操作执行顺序。在顺序一致性内存模型中,每个操作都必须原子执行且对所有线程立即可见。


本博客是对《JAVA并发编程的艺术》方腾飞,魏鹏,程晓明 著。第三章的读后感。

参考资料:[1] intel开发技术手册卷3第2章

时间: 2024-07-28 13:25:27

JAVA 内存模型(一)的相关文章

深入理解Java内存模型(六) final

与前面介绍的锁和volatile相比较,对final域的读和写更像是普通的变量访问.对于final域,编译 器和处理器要遵守两个重排序规则: 在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操 作之间不能重排序. 初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序. 下面,我们通过一些示例性的代码来分别说明这两个规则: public class FinalExample { int i; //普通变量 fin

深入理解Java内存模型(五) 锁

锁的释放-获取建立的happens before 关系 锁是java并发编程中最重要的同步机制.锁除了让 临界区互斥执行外,还可以让释放锁的线程向获取同一个锁的线程发送消息. 下面是锁释放-获取 的示例代码: class MonitorExample { int a = 0; public synchronized void writer() { //1 a++; //2 } //3 public synchronized void reader() { //4 int i = a; //5 -

深入理解Java内存模型(四) volatile

volatile的特性 当我们声明共享变量为volatile后,对这个变量的读/写将会很特别.理解 volatile特性的一个好方法是:把对volatile变量的单个读/写,看成是使用同一个监视器锁对这些单个 读/写操作做了同步.下面我们通过具体的示例来说明,请看下面的示例代码: class VolatileFeaturesExample { volatile long vl = 0L; //使用volatile声明64位的long型变量 public void set(long l) { vl

深入理解Java内存模型(三) 顺序一致性

数据竞争与顺序一致性保证 当程序未正确同步时,就会存在数据竞争.java内存模型规范对数 据竞争的定义如下: 在一个线程中写一个变量, 在另一个线程读同一个变量, 而且写和读没有通过同步来排序. 当代码中包含数据竞争时,程序的执行往往产生违反直觉的结果(前一章的示例正是如此).如果一 个多线程程序能正确同步,这个程序将是一个没有数据竞争的程序. JMM对正确同步的多线程程序 的内存一致性做了如下保证: 如果程序是正确同步的,程序的执行将具有顺序一致性(sequentially consisten

深入理解Java内存模型(二) 重排序

如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存在数据依 赖性.数据依赖分下列三种类型: 上 面三种情况,只要重排序两个操作的执行顺序,程序的执行结果将会被改变. 前面提到过,编译 器和处理器可能会对操作做重排序.编译器和处理器在重排序时,会遵守数据依赖性,编译器和处理器不 会改变存在数据依赖关系的两个操作的执行顺序. 注意,这里所说的数据依赖性仅针对单个处理 器中执行的指令序列和单个线程中执行的操作,不同处理器之间和不同线程之间的数据依赖性不被编译器 和处理器考

深入理解Java内存模型(一) 基础

并发编程模型的分类 在并发编程中,我们需要处理两个关键问题:线程之间如何通信及线程之 间如何同步(这里的线程是指并发执行的活动实体).通信是指线程之间以何种机制来交换信息.在命令 式编程中,线程之间的通信机制有两种:共享内存和消息传递. 在共享内存的并发模型里,线程 之间共享程序的公共状态,线程之间通过写-读内存中的公共状态来隐式进行通信.在消息传递的并发模 型里,线程之间没有公共状态,线程之间必须通过明确的发送消息来显式进行通信. 同步是指程 序用于控制不同线程之间操作发生相对顺序的机制.在共

Java理论与实践: 修复Java内存模型,第2部分

活跃了将近三年的 JSR 133,近期发布了关于如何修复 Java 内存模型 (Java Memory Model, JMM)的公开建议.在本系列文章的 第 1 部分,专栏作 者 Brian Goetz 主要介绍最初的 JMM 中的几个严重缺陷,这些缺陷导致了一些 难度高得惊人的概念语义,这些概念原来被认为很简单.这个月,他介绍在新 JMM 中 volatile 和 final 的语义是如何变化的,这些改变使它们的语义符合 大多数开发人员的直觉.其中一些改变已经在 JDK 1.4 中出现了,另一

Java理论与实践: 修复Java内存模型,第1部分

活跃了将近三年的 JSR 133,近期发布了关于如何修复 Java 内存模型 (Java Memory Model, JMM)的公开建议.原始 JMM 中有几个严重缺陷,这导 致了一些难度高得惊人的概念语义,这些概念原来被认为很简单,如 volatile .final 以及 synchronized.在这一期的 Java 理论与实践 中,Brian Goetz 展示了如何加强 volatile 和 final 的语义,以修复 JMM.这些更改有些已经 集成在 JDK 1.4 中:而另一些将会包含

聊聊我对Java内存模型的理解

所有的编程语言中都有内存模型这个概念,区别于微架构的内存模型,高级语言的内存模型包括了编译器和微架构两部分.我试图了解了Java.C#和Go语言的内存模型,发现内容基本大同小异,只是这些语言在具体实现的时候略有不同. 我们来看看Java内存模型吧,提到Java内存模型大家对这个图一定非常熟悉: 这张图告诉我们在线程运行的时候有一个内存专用的一小块内存,当Java程序会将变量同步到线程所在的内存,这时候会操作工作内存中的变量,而线程中变量的值何时同步回主内存是不可预期的.但同时Java内存模型又告

同步与Java内存模型(一)序言

原文:http://gee.cs.oswego.edu/dl/cpj/jmm.html 作者:Doug Lea 译者:萧欢  校对:丁一,方腾飞 先来看如下这个简单的Java类,该类中并没有使用任何的同步. 查看源代码 打印帮助 01 final class SetCheck { 02 private int  a = 0; 03 private long b = 0; 04   05 void set() { 06 a =  1; 07 b = -1; 08 } 09   10 boolean