对Java内存模型即JMM的理解

类似物理上的计算机系统,Java虚拟机规范中也定义了一种Java内存模型,即Java Memory Model(JMM),
来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的并发效果。


现在最新的Java内存模型规范是JSR-133,即Java内存模型与线程规范,这套规范包含:

  • 线程之间如何通过内存通信;
  • 线程之间通过什么方式通信才合法,才能得到期望的结果。

理解内存模型对Java的并发编程有很大的帮助。

Java内存模型

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

(1)主内存和本地内存
主内存即main memory。在java中,实例域、静态域和数组元素是线程之间共享的数据,它们存储在主内存中。
本地内存即local memory。 局部变量,方法定义参数 和 异常处理器参数是不会在线程之间共享的,它们存储在线程的本地内存中。
(2)原子性
是指一个操作是按原子的方式执行的。要么该操作不被执行;要么以原子方式执行,即执行过程中不会被其它线程中断。

重排序

重排序是指“编译器和处理器”为了提高性能,而在程序执行时会对程序进行的重排序。
重排序分为——“编译器”和“处理器”两个方面,而“处理器”重排序又包括“指令级重排序”和“内存的重排序”。
关于重排序,我们需要理解它的思想:为了提高程序的并发度,从而提高性能!但是对于多线程程序,重排序可能会导致程序执行的结果不是我们需要的结果!因此,就需要我们通过“volatile,synchronize,锁等方式”作出正确的实现同步。

内存屏障

内存屏障包括LoadLoad, LoadStore, StoreLoad, StoreStore共4种内存屏障。内存屏障是与相应的内存重排序相对应的。
通过内存屏障可以禁止特定类型处理器的重排序,从而让程序按我们预想的流程去执行。

顺序一致性

顺序一致性内存模型是理想化的内存模型。有以下规则:
(1)一个线程中的所有操作必须按照程序的顺序来执行。
(2)所有线程都只能看到一个单一的操作执行顺序。在顺序一致性内存模型中,每个操作都必须原子执行且立刻对所有线程可见。

锁是java并发编程中最重要的同步机制。
(1)锁的内存语义:
线程A释放一个锁,实质上是线程A向接下来将要获取这个锁的某个线程发出了(线程A对共享变量所做修改的)消息。
线程B获取一个锁,实质上是线程B接收了之前某个线程发出的(在释放这个锁之前对共享变量所做修改的)消息。
线程A释放锁,随后线程B获取这个锁,这个过程实质上是线程A通过主内存向线程B发送消息。

 

(2)JMM如何实现锁
公平锁是通过“volatile”实现同步的。公平锁在释放锁的最后写volatile变量state;在获取锁时首先读这个volatile变量。根据volatile的happens-before规则,释放锁的线程在写volatile变量之前可见的共享变量,在获取锁的线程读取同一个volatile变量后将立即变的对获取锁的线程可见。

非公平锁通过CAS实现的,CAS就是compare and swap。CAS实际上调用的JNI函数,也就是CAS依赖于本地实现。以Intel来说,对于CAS的JNI实现函数,它保证:
禁止该CAS之前和之后的读和写指令重排序。
把写缓冲区中的所有数据刷新到内存中。

 

final域

对于基本类型的final域,编译器和处理器要遵守两个重排序规则:
(1)final写:“构造函数内对一个final域的写入”,与“随后把这个被构造对象的引用赋值给一个引用变量”,这两个操作之间不能重排序。
(2)final读:“初次读一个包含final域的对象的引用”,与“随后初次读对象的final域”,这两个操作之间不能重排序。
对于引用类型的final域,除上面两条之外,还有一条规则:
(3)final写:在“构造函数内对一个final引用的对象的成员域的写入”,与“随后在构造函数外把这个被构造对象的引用赋值给一个引用变量”,这两个操作之间不能重排序。
写final域的重排序规则可以确保在引用变量为任意线程可见之前,该引用变量指向的对象的final域已经在构造函数中被正确初始化过了。其实要得到这个效果,还需要一个保证:在构造函数内部,不能让这个被构造对象的引用为其他线程可见,也就是对象引用不能在构造函数中“逸出”。
JMM通过“内存屏障”实现final,在final域的写之后,构造函数return之前,插入一个StoreStore障屏。在读final域的操作前面插入一个LoadLoad屏障。

 

参考 InfoQ·深入理解Java内存模型

posted @ 2015-11-03 19:55  邴越  阅读(766)  评论(0编辑  收藏  举报