`
wen866595
  • 浏览: 268472 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

Java 内存模型 JMM

    博客分类:
  • java
阅读更多

 

原文地址:http://coderbee.net/index.php/concurrent/20131219/650

 

JMM,Java Memory Model,Java 内存模型。

什么是内存模型,要他何用?

假定一个线程为变量var赋值:var = 3;,内存模型要回答的问题是:在什么条件下,读取变量var的线程可以看到3这个值?

如果缺少了同步,线程可能无法看到其他线程操作的结果。导致这种情况的原因可以有:编译器生成指令的次序可以不同于源代码的“显然”版本,编译器还 会把变量存储在寄存器而不是内存中;处理器可以乱序或并行执行指令;缓存会改变写入提交到主存得到变量的次序;存储在处理器本地缓存中的变量对其他处理器 不可见 等等。

数据依赖性

如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存在数据依赖性。数据依赖分下列三种类型:

名称   代码示例     说明
写后读  a = 1;b = a;   写一个变量之后,再读这个位置。
写后写  a = 1;a = 2;   写一个变量之后,再写这个变量。
读后写  a = b;b = 1;   读一个变量之后,再写这个变量。

上面三种情况,只要重排序两个操作的执行顺序,程序的执行结果将会被改变。

编译器和处理器在重排序时,会遵守数据依赖性,编译器和处理器不会改变存在数据依赖关系的两个操作的执行顺序。

as-if-serial语义

as-if-serial语义的意思指:不管怎么重排序,(单线程)程序的执行结果不能被改变。编译器、runtime 和处理器都必须遵守as-if-serial语义。

数据竞争

当程序未正确同步时,就会存在数据竞争。java内存模型规范对数据竞争的定义为:在一个线程中写一个变量,在另一个线程读同一个变量,而且写和读没有通过同步来排序。

顺序一致性模型

操作执行的顺序是唯一的,就是它们出现在程序中的顺序,这与执行它们的处理器无关;变量每一次读操作,都能得到执行序列上这个变量最新的写入值,无论这是哪个处理器写入的。这个是一个理想的模型,JMM是不支持的。

Java 语言规范规定了 JVM 要维护内部线程类似顺序语意(within-thread as-if-serial semantics):只要程序的最终结果等同于它在严格的顺序环境中执行的结果,那么上述所有的行为都是允许的。

JMM 规定了 JVM 的一种最小保证:什么时候写入一个变量会对其他线程可见。

Java 内存模型

Java 内存模型的定义是通过动作(actions)的形式进行描述的,所谓动作,包括变量读和写、监视器加锁和释放锁、线程的启动和拼接(join)。

JMM为所有程序内部的动作定义了一个叫 happens-before 的偏序关系(偏序关系是反对称的、自反的和传递的关系)。如果操作 A 和 B 满足 happens-before 关系,那么执行动作 B 的线程就可以看到动作 A 的结果;如果两个操作之间没有 happens-before 关系,那么 JVM 就可以对它们随意地重排序。

happens-before法则

  • 程序次序法则:线程中的每个动作 A 都 happens-before 于该线程中的每一个动作 B,其中,在程序中,所有的动作 B 都出现在动作 A 之后。(注:此法则只是要求遵循 as-if-serial语义)

  • 监视器锁法则:对一个监视器锁的解锁 happens-before 于每一个后续对同一监视器锁的加锁。(显式锁的加锁和解锁有着与内置锁,即监视器锁相同的存储语意。)

  • volatile变量法则:对 volatile 域的写入操作 happens-before 于每一个后续对同一域的读操作。(原子变量的读写操作有着与 volatile 变量相同的语意。)(volatile变量具有可见性和读写原子性。)

  • 线程启动法则:在一个线程里,对 Thread.start 的调用会 happens-before 于每一个启动线程中的动作。

  • 线程终止法则:线程中的任何动作都 happens-before 于其他线程检测到这个线程已终结,或者从 Thread.join 方法调用中成功返回,或者 Thread.isAlive 方法返回false。

  • 中断法则法则:一个线程调用另一个线程的 interrupt 方法 happens-before 于被中断线程发现中断(通过抛出InterruptedException, 或者调用 isInterrupted 方法和 interrupted 方法)。

  • 终结法则:一个对象的构造函数的结束 happens-before 于这个对象 finalizer 开始。

  • 传递性:如果 A happens-before 于 B,且 B happens-before 于 C,则 A happens-before 于 C。

对于final域,编译器和处理器要遵守两个重排序规则:

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

注意:

  • 关于 final 域的内存语义,是在没有 this 引用逸出的前提下的。
  • 对于依赖监视器锁法则的内存可见性,对共享变量变量的所有访问都必须在同步的情况下才有意义。

对于Java编程来说,需要关注的法则有:程序次序法则、监视器锁法则、volatile变量法则,还有 final 域的规则。

final 域与 this 引用逸出

this 引用逸出的举例:

public class ThisEscape {
       private final int a; // final 域

       public ThisEscape( int init, List<Object> list) {
             // 对 final 域赋值, JMM 不保证这个写之后有 store barrier
             a = init;

             // 把 this 添加到一个外部集合,导致 this 引用逸出。
             // 其他线程通过这个逸出的引用看到的 a 可能仍然是未初始化的值。
            list.add( this );

             // ...  其他构造语句

             // 构造函数结束:   store barrier; return;
      }
}

JMM 对 final 域的保证可理解为它只会在构造函数返回之前插入一个存储屏障,保证构造函数内对 final 域的赋值在构造函数返回之前写到主存。

happens-before 举例说明

public class JMM {
       int a ;
       int b ;
       int multi ;
       volatile int sum ; //

       public void order() {
             int c = 12;   // 1
             int d = 34;   // 2
             // 作为方法内的局部变量,c、d 只能被当前线程访问,不存在数据竞争;
             // 步骤 1 和 2 没有数据依赖,且它们之间没有限制重排序的操作,所以这两步之间 可以自由重排序。

             multi = c * d; // 3
             // 3 和 步骤 1、2 之间有数据依赖,所以 3 和 1、2 组成的整体之间不能重排序。
             // 1、2 可能重排序了,所以说是整体。

             sum = c + d;  // 5
             // insert store barrier here   !!!!!
             // 步骤 5 与 3 之间虽然没有数据依赖,但 volatile 的语义禁止两者之间的重排序。
             // 执行完 store barrier 之后,其他线程都可以看到当前线程写入 sum 和 multi 的最新值。

             int doubleTemp = multi * 2;  // 5
             // multi 是对象的属性,是共享变量,
             // 如果有另一个线程进行写操作,由于对 multi 的访问没有进行同步,与当前线程的读操作存在数据竞争。
             // 所以在多线程的情况下步骤 5 的执行结果是不确定的。

             // insert load barrier here   !!!!!
             // volatile 变量的读操作之前会作废当前 CPU 的本地缓存,后续变量的访问需要重新从主存读取。
             int doubleSum = sum * 2;  // 8
             // 由于 volatile 变量不具有互斥性,且当前方法没有使用锁进行同步,
             // 所以步骤 8 读到的 sum 的值可能不是 步骤 5 写入的值(被其他线程修改了)。
      }
}

深入理解 Java 内存模型是理解 JUC 包和编写高性能并发程序的基础。

 

可以通过JUC里同步器及其应用的源码分析去感受下内存模型的应用:

 

JUC AQS: http://coderbee.net/index.php/concurrent/20131205/600

JUC 源码分析 一 AbstractQueuedSynchronizer:http://coderbee.net/index.php/concurrent/20131209/614

JUC 源码分析 二 ReentrantLock:http://coderbee.net/index.php/concurrent/20131209/618

JUC 源码分析 三 AbstractQueuedSynchronizer 共享模式 与 CountDownLatch:http://coderbee.net/index.php/concurrent/20131213/631

JUC 源码分析 四 wait notify notifyAll 与 条件对象:http://coderbee.net/index.php/concurrent/20131220/653

 

参考资料

0
0
分享到:
评论

相关推荐

    java内存模型JMM(Java Memory Model)1

    Java内存模型(JMM,Java Memory Model)是Java平台中用于描述如何在多线程环境中管理内存的一套规范。它确保了并发编程时不同线程之间的数据一致性、可见性和原子性,以避免出现数据竞争和其他并发问题。以下是JMM...

    Java内存模型详解JMM.docx

    Java内存模型详解JMM Java内存模型(Java Memory Model,JMM)是Java虚拟机(JVM)中的一种内存模型,它描述了程序中各个变量之间的关系,以及在实际计算机系统中将变量存储到内存和从内存中取出变量这样的底层细节...

    学习Java内存模型JMM心得

    Java内存模型JMM心得 Java内存模型(JMM)是Java虚拟机规范中的一部分,对Java编程语言的内存模型进行了抽象,解决了编译器和处理器的优化对多线程程序的影响。JMM的主要目标是提供一个在多线程环境下正确的内存...

    Java内存模型JMM详解

    主要介绍了Java内存模型JMM详解,涉及volatile和监视器锁,final字段,内存屏障等相关内容,具有一定参考价值,需要的朋友可以了解下。

    深入Java内存模型-JMM

    Java内存模型,简称JMM(Java Memory Model),是Java虚拟机规范中定义的一个抽象概念,它描述了在多线程环境下,如何保证各个线程对共享数据的一致性视图。JMM的主要目标是定义程序中各个变量的访问规则,以及在...

    深入理解Java内存模型 pdf 超清版

    Java内存模型,简称JMM(Java Memory Model),是Java编程语言规范的一部分,它定义了程序中各个线程如何访问和修改共享变量,以及如何确保数据的一致性。深入理解Java内存模型对于编写高效的并发程序至关重要。本文...

    深入理解Java内存模型

    Java内存模型是并发编程中一个至关重要的概念,它定义了共享变量的访问规则,以及这些变量如何在多线程环境下进行读写操作。在深入理解Java内存模型之前,我们需要先了解并发编程模型的分类,然后掌握Java内存模型的...

    Java内存模型JMM浅析

     并发编程有多种风格,除了CSP(通信顺序进程)、Actor等模型外,大家熟悉的应该是基于线程和锁的共享内存模型了。在多线程编程中,需要注意三类并发问题:  1、原子性  2、可见性  3、重排序  原子性涉及到...

    Java内存模型(JMM)深度解析:原理、特性与代码实践

    Java内存模型(Java Memory Model,简称JMM)是Java并发编程中的核心概念,它定义了Java程序中多线程间共享变量的访问规则。理解JMM对于编写正确、高效的并发程序至关重要。本文将深入探讨JMM的原理、特性以及如何在...

    深入理解 Java 内存模型_程晓明_InfoQ_java_内存模型_

    Java内存模型,简称JMM(Java Memory Model),是Java编程语言规范的一部分,它定义了线程如何共享和访问内存,以及在多线程环境中如何保证数据一致性。理解JMM对于编写高效、正确且线程安全的Java代码至关重要。 ...

    java内存模型文档

    Java内存模型,简称JMM(Java Memory Model),是Java编程语言规范的一部分,它定义了线程如何共享和访问内存,以及在并发编程中如何处理数据一致性的问题。理解JMM对于编写高效、线程安全的Java代码至关重要。 1. ...

    Java内存模型的历史变迁

    Java内存模型(Java Memory Model,简称JMM)作为Java并发机制的核心,其设计理念直接影响到程序的性能与可靠性。本文将探讨Java内存模型从早期版本到JDK 5的重大变革,并重点介绍这一变迁背后的动机及其对Java开发...

    深入理解 Java 内存模型

    Java 内存模型(Java Memory Model,简称 JMM)是 Java 平台中关于线程如何访问共享变量的一套规则,它定义了线程之间的内存可见性、数据一致性以及指令重排序等关键概念,对于多线程编程和并发性能优化至关重要。...

    深入理解java内存模型

    Java内存模型(Java Memory Model,JMM)是Java平台中非常关键的概念,它定义了线程如何共享和访问内存中的数据,以及在多线程环境下如何保证数据的一致性。这本书"深入理解Java内存模型"显然是为了帮助读者深入探讨...

    《深入理解JAVA内存模型》PDF

    Java线程之间的通信由Java内存模型(本文简称为JMM)控制,JMM决定一个线程对共享变量的写入何时对另一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main ...

    深度剖析java内存模型

    Java内存模型(Java Memory Model,简称JMM)是Java虚拟机(JVM)规范中定义的一种内存模型,它涉及了线程之间共享变量的可见性问题。在并发编程中,理解Java内存模型对于编写正确的多线程程序至关重要。 首先,...

    java内存模型.pdf

    标题和描述中提及的知识点主要围绕Java内存模型(JMM),JVM内存结构,包括堆栈讲解,以及本机内存管理等内容。以下是对这些知识点的详细阐述: ### Java内存模型(JMM) #### JMM简介 Java内存模型(JMM)是Java虚拟机...

Global site tag (gtag.js) - Google Analytics