java内存模型

作者: jijs | 来源:发表于2017-04-28 21:24 被阅读971次

    前言

    在学习java多线程并发编程前,必须要了解java内存模型,只有了解java内存模型,才能知道为什么多线程并发时会出现数据不一致,什么时候需要加锁同步等各种问题。下面只是简单阐述下java内存模型及其相关的概念。

    内存模型简介

    java的并发采用的是共享内存模型(而非消息传递模型)。

    Java内存模型(Java Memory Model)描述了Java程序中各种变量(共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取变量这样的底层细节。

    Java线程之间的通信由Java内存模型(JMM)控制,JMM决定一个线程对共享变量的写入何时对另一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在,它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。Java内存模型的抽象示意图如下:


    内存模型

    图中的共享变量为:实例变量和静态变量。(局部变量是线程私有的不存在竞争)

    从图中看,线程A和线程B进行通讯必须通过主内存

    1. 线程A修改了一个共享变量,并刷新到主内存中
    2. 线程B从主内存读取A修改过的共享变量。

    注意:

    1. 线程对共享变量的所有操作都必须在自己的本地内存中进行,不能直接从主内存中读写。
    2. 不同线程之间无法直接访问其它线程的本地内存,线程间的变量值的传递,必须通过主内存来完成。

    指令重排序

    指令重排序是JVM为了优化指令,提高程序运行效率,在不影响单线程程序执行结果的前提下,尽可能地提高并行度。编译器、处理器也遵循这样一个目标。注意是单线程。多线程的情况下指令重排序就会给程序员带来问题。

    重排序分三种类型:


    Paste_Image.png
    1. 编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
    2. 指令级并行的重排序。现代处理器采用了指令级并行技术(Instruction-Level Parallelism, ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
    3. 内存系统的重排序。由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。

    内存可见性

    内存可见性简单描述:当主内存中的一个共享变量在多个线程的本地内存中都存在副本,如果一个线程修改共享变量,其它线程也应该能看到被修改后的值。

    要实现共享变量的可见性,必须实现两点:

    1. 线程修改后的共享变量值能够及时的从工作内存刷新到主内存中。
    2. 其它线程能够及时把共享变量的最新值从主内存更新到自己的本地内存中。

    happens-before

    JMM使用happens-before的概念来阐述操作之间的内存可见性。在JMM中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作就必须存在happens-before关系。

    happens-before规则如下:

    1. 程序顺序规则:一个线程中的每个操作,happens- before 于该线程中的任意后续操作。
    2. 监视器锁规则:对一个监视器锁的解锁,happens- before 于随后对这个监视器锁的加锁。
    3. volatile变量规则:对一个volatile域的写,happens- before 于任意后续对这个volatile域的读。
    4. 传递性:如果A happens- before B,且B happens- before C,那么A happens- before C。

    注意,两个操作之间具有happens-before关系,并不意味着前一个操作必须要在后一个操作之前执行!happens-before仅仅要求前一个操作(执行的结果)对后一个操作可见,且前一个操作按顺序排在第二个操作之前。

    as-if-serial

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

    例如:

    int a=2;            //A
    int b=3;            //B
    int c=a*b;          //C
    
    Paste_Image.png

    注意:

    1. 重排序不会给单线程程序带来内存可见性问题
    2. 多线程程序并发交叉执行时,重排序可能会造成内存可见性问题

    上面程序的happens-before的关系:

    1. A happens-before B
    2. B happens-before C
    3. A happens-before C (happens-before的传递性)

    在这里A happens-before B, 但实际执行B可以排在A之前执行。JMM仅仅要求前一个操作对后一个操作可见,且前一个操作按顺序排在第二个操作之前。这里A的执行结果不需要对操作B可见。


    想了解更多精彩内容请关注我的公众号

    相关文章

      网友评论

      • jijs:底层使用内存屏障来实现happen-before的规则(禁止指令重排),使用lock前缀指令来保证内存可见性,后面会写synchronize,volatile,final相关的文章,程序员可以用他来控制指令重排序和内存可见性。
      • 无_4a93:happens before只是说了规则。但是内存管理如何实现的规则呢?
        jijs: @无_4a93 底层使用内存屏障来实现happen-before的规则(禁止指令重排),使用lock前缀指令来保证内存可见性,后面会写synchronize,volatile,final相关的文章,程序员可以用他来控制指令重排序和内存可见性。

      本文标题:java内存模型

      本文链接:https://www.haomeiwen.com/subject/bhpbzttx.html