`
shiyixunsyx
  • 浏览: 3460 次
  • 性别: Icon_minigender_1
  • 来自: 北京
最近访客 更多访客>>
社区版块
存档分类
最新评论

Java内存模型-jsr133规范介绍

    博客分类:
  • java
阅读更多

Java内存模型-jsr133规范介绍

 

最近在看《深入理解Java虚拟机:JVM高级特性与最佳实践》讲到了线程相关的细节知识,里面讲述了关于java内存模型,也就是jsr 133定义的规范。

系统的看了jsr 133规范的前面几个章节的内容,觉得受益匪浅。废话不说,简要的介绍一下java内存规范。

什么是内存规范

在jsr-133中是这么定义的

A memory model describes, given a program and an execution trace of that program, whether
the execution trace is a legal execution of the program. For the Java programming language, the
memory model works by examining each read in an execution trace and checking that the write
observed by that read is valid according to certain rules.

也就是说一个内存模型描述了一个给定的程序和和它的执行路径是否一个合法的执行路径。对于java序言来说,内存模型通过考察在程序执行路径中每一个读操作,根据特定的规则,检查写操作对应的读操作是否能是有效的。

java内存模型只是定义了一个规范,具体的实现可以是根据实际情况自由实现的。但是实现要满足java内存模型定义的规范。

 

处理器和内存的交互

这个要感谢硅工业的发展,导致目前处理器的性能越来越强大。目前市场上基本上都是多核处理器。如何利用多核处理器执行程序的优势,使得程序性能得到极大的提升,是目前来说最重要的。

目前所有的运算都是处理器来执行的,我们在大学的时候就学习过一个基本概念  程序 = 数据 + 算法 ,那么处理器负责计算,数据从哪里获取了?

数据可以存放在处理器寄存器里面(目前x86处理都是基于寄存器架构的),处理器缓存里面,内存,磁盘,光驱等。处理器访问这些数据的速度从快到慢依次为:寄存器,处理器缓存,内存,磁盘,光驱。为了加快程序运行速度,数据离处理器越近越好。但是寄存器,处理器缓存都是处理器私有数据,只有内存,磁盘,光驱才是才是所有处理器都可以访问的全局数据(磁盘和光驱我们这里不讨论,只讨论内存)如果程序是多线程的,那么不同的线程可能分配到不同的处理器来执行,这些处理器需要把数据从主内存加载到处理器缓存和寄存器里面才可以执行(这个大学操作系统概念里面有介绍),数据执行完成之后,在把执行结果同步到主内存。如果这些数据是所有线程共享的,那么就会发生同步问题。处理器需要解决何时同步主内存数据,以及处理执行结果何时同步到主内存,因为同一个处理器可能会先把数据放在处理器缓存里面,以便程序后续继续对数据进行操作。所以对于内存数据,由于多处理器的情况,会变的很复杂。下面是一个例子:

 

初始值 a = b = 0

process1           process2

1:load a               5:load b

2:write a:2           6:add b:1

3:load b               7: load a

4:write b:1           8:write a:1

假设处理器1先加载内存变量a,写入a的值为2,然后加载b,写入b的值为1,同时 处理2先加载b,执行b+1,那么b在处理器2的结果可能是1 可能是3。因为在load b之前,不知道处理器1是否已经吧b写会到主内存。对于a来说,假设处理器1后于处理器2把a写会到主内存,那么a的值则为2。

而内存模型就是规定了一个规则,处理器如何同主内存同步数据的一个规则。

内存模型介绍

在介绍java内存模型之前,我们先看看两个内存模型

Sequential Consistency Memory Model:连续一致性模型。这个模型定义了程序执行的顺序和代码执行的顺序是一致的。也就是说 如果两个线程,一个线程T1对共享变量A进行写操作,另外一个线程T2对A进行读操作。如果线程T1在时间上先于T2执行,那么T2就可以看见T1修改之后的值。

这个内存模型比较简单,也比较直观,比较符合现实世界的逻辑。但是这个模型定义比较严格,在多处理器并发执行程序的时候,会严重的影响程序的性能。因为每次对共享变量的修改都要立刻同步会主内存,不能把变量保存到处理器寄存器里面或者处理器缓存里面。导致频繁的读写内存影响性能。

 

Happens-Before Memory Model : 先行发生模型。这个模型理解起来就比较困难。先介绍一个现行发生关系 (Happens-Before Relationship

  如果有两个操作A和B存在A Happens-Before B,那么操作A对变量的修改对操作B来说是可见的。这个现行并不是代码执行时间上的先后关系,而是保证执行结果是顺序的。看下面例子来说明现行发生

A,B为共享变量,r1,r2为局部变量
 
初始 A=B=0
 
Thread1   | Thread2
1: r2=A   | 3: r1=B
2: B=2    4: A=2

  凭借直观感觉,线程1先执行 r2=A,则r2=0 ,然后赋值B=1,线程2执行r1=B,由于线程1修改了B的值为1,所以r1=1。但是在现行发生内存模型里面,有可能最终结果为r1 = r2 = 2。为什么会这样,因为编译器或者多处理器可能对指令进行乱序执行,线程1 从代码流上面看是先执行r2 = A,B = 1,但是处理器执行的时候会先执行 B = 2 ,在执行 r2 = A,线程2 可能先执行 A = 2 ,在执行r1 = B,这样可能 会导致 r1 = r2 = 2。

那我们先看看先行发生关系的规则

  • 1 在同一个线程里面,按照代码执行的顺序(也就是代码语义的顺序),前一个操作先于后面一个操作发生
  • 2 对一个monitor对象的解锁操作先于后续对同一个monitor对象的锁操作
  • 3 对volatile字段的写操作先于后面的对此字段的读操作
  • 4 对线程的start操作(调用线程对象的start()方法)先于这个线程的其他任何操作
  • 5 一个线程中所有的操作先于其他任何线程在此线程上调用 join()方法
  • 6 如果A操作优先于B,B操作优先于C,那么A操作优先于C

解释一下以上几个先行发生规则的含义

规则1应该比较好理解,因为比较适合人正常的思维。比如在同一个线程t里面,代码的顺序如下:

thread 1
共享变量A、B
局部变量r1、r2
 
代码顺序
1: A =1
2: r1 = A
3: B = 2
4: r2 = B
 
执行结果 就是 A=1 ,B=2 ,r1=1 ,r2=2

因为以上是在同一个线程里面,按照规则1 也就是按照代码顺序,A = 1 先行发生 r1 =A ,那么r1 = 1

再看规则2,下面是jsr133的例子

按照规则2,由于unlock操作先于发生于lock操作,所以X=1对线程2里面就是可见的,所以r2 = 1

在分析以下,看这个例子,由于unlock操作先于lock操作,所以线程x=1对于线程2不一定是可见(不一定是现行发生的),所以r2的值不一定是1,有可能是x赋值为1之前的那个状态值(假设x初始值为0,那么此时r2的值可能为0)

对于规则3,我们可以稍微修改一下我们说明的第一个例子

A,B为共享变量,并且B是valotile类型的
r1,r2为局部变量
 
初始 A=B=0
 
Thread1   | Thread2
1: r2=A   | 3: r1=B
2: B=2    4: A=2
 
那么r1 = 2, r2可能为0或者2

 因为对于volatile类型的变量B,线程1对B的更新马上线程2就是可见的,所以r1的值就是确定的。由于A是非valotile类型的,所以值不确定。

规则4,5,6这里就不解释了,知道规则就可以了。

 可以从以上的看出,先行发生的规则有很大的灵活性,编译器可以对指令进行重新排序,以便满足处理器性能的需要。只要重新排序之后的结果,在单一线程里面执行结果是可见的(也就是在同一个线程里面满足先行发生原则1就可以了)。

java内存模型是建立在先行发生的内存模型之上的,并且再此基础上,增强了一些。因为现行发生是一个弱约束的内存模型,在多线程竞争访问共享数据的时候,会导致不可预期的结果。有一些是java内存模型可以接受的,有一些是java内存模型不可以接受的。具体细节这里面就不详细说明了。这里只说明关于java新的内存模型重要点。

final字段的语义

在java里面,如果一个类定义了一个final属性,那么这个属性在初始化之后就不可以在改变。一般认为final字段是不变的。在java内存模型里面,对final有一个特殊的处理。如果一个类C定义了一个非static的final属性A,以及非static final属性B,在C的构造器里面对A,B进行初始化,如果一个线程T1创建了类C的一个对象co,同一时刻线程T2访问co对象的A和B属性,如果t2获取到已经构造完成的co对象,那么属性A的值是可以确定的,属性B的值可能还未初始化,

下面一段代码演示了这个情况

public class FinalVarClass {
 
    public final int a ;
    public int b = 0;
     
    static FinalVarClass co;
     
    public FinalVarClass(){
        a = 1;
        b = 1;
    }
     
    //线程1创建FinalVarClass对象 co
    public static void create(){
        if(co == null){
            co = new FinalVarClass();
        }
    }
     
    //线程2访问co对象的a,b属性
    public static void vistor(){
        if(co != null){
            System.out.println(co.a);//这里返回的一定是1,a一定初始化完成
            System.out.println(co.b);//这里返回的可能是0,因为b还未初始化完成
        }
    }
}

为什么会发生这种情况,原因可能是处理器对创建对象的指令进行重新排序。正常情况下,对象创建语句co = new FinalVarClass()并不是原子的,简单来说,可以分为几个步骤,1 分配内存空间 2 创建空的对象 3 初始化空的对象 4 把初始化完成的对象引用指向 co ,由于这几个步骤处理器可能并发执行,比如3,4 并发执行,所以在create操作完成之后,co不一定马上初始化完成,所以在vistor方法的时候,b的值可能还未初始化。但是如果是final字段,必须保证在对应返回引用之前初始化完成。

volatile语义

对于volatile字段,在现行发生规则里面已经介绍过,对volatile变量的写操作先于对变量的读操作。也就是说任何对volatile变量的修改,都可以在其他线程里面反应出来。对于volatile变量的介绍可以参考 本人写的一篇文章 《java中volatile关键字的含义》 里面有详细的介绍。

volatile在java新的内存规范里面还加强了新的语义。在老的内存规范里面,volatile变量与非volatile变量的顺序是可以重新排序的。举个例子

public class VolatileClass {
 
    int              x = 0;
    volatile boolean v = false;
 
    //线程1write
    public void writer() {
        x = 42;
        v = true;
    }
    //线程2 read
    public void reader() {
        if (v == true) {
            System.out.println(x);//结果可能为0,可能为2
        }
    }
}

 线程1先调用writer方法,对x和v进行写操作,线程reader判断,如果v=true,则打印x。在老的内存规范里面,可能对v和x赋值顺序发生改变,导致v的写操作先行于x的写操作执行,同时另外一个线程判断v的结果,由于v的写操作先行于v的读操作,所以if(v==true)返回真,于是程序执行打印x,此时x不一定先行与System.out.println指令之前。所以显示的结果可能为0,不一定为2

但是java新的内存模型jsr133修正了这个问题,对于volatile语义的变量,自动进行lock 和 unlock操作包围对变量volatile的读写操作。那么以上语句的顺序可以表示为

 

thread1              thread2
1 :write x=1        5:lock(m)
 
2 :lock(m)          6:read v
 
3 :write v=true     7:unlock(m)
 
4 :unlock            8 :if(v==true)
 
                     9: System.out.print(x)

 由于unlock操作先于lock操作,所以x写操作5先于发生x的读操作9

 

以上只是jsr规范中一些小结行的内容,由于jsr133规范定义了很多术语以及很多推论,上述只是简单的介绍了一些比较重要的内容,具体细节可以参考jsr规范的public view :http://today.java.net/pub/a/today/2004/04/13/JSR133.html

分享到:
评论

相关推荐

    java 内存模型 jsr-133

    ### Java内存模型JSR-133详解 #### 引言 Java内存模型(Java Memory Model,简称JMM)是Java编程语言中的一个重要概念,它规定了程序中各种变量(线程共享变量)的访问规则,以及在并发环境中如何解决数据一致性...

    JSR133中文版.pdf

    本文是JSR-133规范,即JavaTM内存模型与线程规范,由JSR-133专家组开发。本规范是JSR-176(定义了JavaTM平台 Tiger(5.0)发布版的主要特性)的一部分。本规范的标准内容将合并到JavaTM语言规范、JavaTM虚拟机规范...

    Java 内存模型

    JSR-133是Java社区的一个规范,它的全称是Java内存模型和线程规范最终草稿。这项规范是Java 1.5(代号Tiger)版本的一部分,它继承了JSR-176项目。该规范修订了Java语言规范第二版第17章和Java虚拟机规范第二版第8...

    JSR133中文版

    **JSR133中文版** 是一份关于Java内存模型(Java Memory Model,JMM)的重要文档,它详细解释了JSR(Java Specification Requests)133中的规范,该规范对Java平台的并发编程有着深远的影响。JSR是Java社区进程...

    JSR133(JavaTM内存模型与线程规范).zip

    JSR-133 :Java TM 内存模型与线程规范

    JSR133中文版,JSR133中文版

    JSR-133 Java内存模型和线程规范详解 JSR-133是Java Community Process(Java社区进程)中的一个规范,负责定义Java语言的内存模型和线程规范。该规范的主要目的是为了确保在多线程环境下,Java程序的执行结果是可...

    JSR-133-ZH.pdf

    6. **Java内存模型的近似模型**:JSR-133介绍了两个近似模型来解释JMM的实际效果。**顺序一致的内存模型**是一种理论上的极限情况,而**Happens-Before**关系则是实际JMM的基础,它定义了一组规则,指示哪些操作必须...

    深入理解Java内存模型

    JSR-133规范增强了volatile的内存语义,确保了volatile变量的有序性和可见性,这使得volatile在多线程程序中被广泛使用。 综上所述,Java内存模型是一个复杂的系统,它涉及了线程间的通信与同步、共享变量的访问...

    JSR-133-JMM.zip

    本文是JSR-133规范,即JavaTM内存模型与线程规范,由JSR-133专家组开发。本规范是JSR-176(定义了JavaTM平台 Tiger(5.0)发布版的主要特性)的一部分。本规范的标准内容将合并到JavaTM语言规范、JavaTM虚拟机规范...

    java内存模型的介绍和说明

    ### Java内存模型的介绍和说明 #### JMM 简介 Java Memory Model(JMM),即Java内存模型,是Java虚拟机规范中定义的一套规定,用于描述Java程序中的各种变量(包括实例字段、静态字段和数组元素)的访问规则,...

    Java内存模型详解

    为了改进原始JMM的不足之处,Java社区通过JSR 133(Java Specification Request 133)提出了新的Java内存模型标准。JSR133的目标在于: - **保留类型安全性**:确保编译器和JVM在执行优化时不会破坏程序的语义。 - ...

    JSR133中文版,java并发进阶必读

    JSR133,全称为Java Specification Request 133,是Java平台标准版(Java SE)的一个重要更新,它主要关注Java内存模型(Java Memory Model, JMM)的改进。这个规范对于理解Java并发编程至关重要,因为它解决了多...

    jsr133_content.pdf

    Java内存模型(JMM)是Java程序并发执行时的核心规范,由JSR-133专家小组开发。JSR-133全称为JavaTM Memory Model and Thread Specification,它的目标是确保多线程环境下程序的可见性和一致性。该规范与Java平台...

    java内存模型

    Java内存模型(Java Memory Model,JMM)是Java虚拟机(JVM)规范中的一个重要组成部分,它定义了程序中各个变量(包括实例域、静态域和数组元素)的访问规则,以及在实际计算机系统中如何将这些变量存储在内存和从...

    Java内存模型详解JMM.docx

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

    java内存模型.pdf

    JSR133是Java内存模型的规范,旨在解决早期JMM中存在的问题。其目标是提供一个跨平台、一致的内存模型,同时保证类型安全,避免出现“out-of-thin-air”(无中生有)的错误,简化多线程编程,并支持多种硬件架构上的...

    JSR133 FAQ 中文版

    ### JSR133 FAQ中文版关键...综上所述,JSR133是Java内存模型的重要改进,它解决了原有模型中的许多问题,提高了程序的可靠性和可移植性。对于从事Java开发的工程师来说,深入理解JSR133及其背后的概念是非常必要的。

Global site tag (gtag.js) - Google Analytics