Java内存模型(Java Memory Model,JMM)是一种抽象的概念,它定义了Java虚拟机(JVM)在计算机内存中如何执行操作,尤其是在并发编程中,如何处理多线程之间的交互、同步以及数据一致性问题。JMM解决了并发编程中的内存可见性、原子操作、有序性等问题,并提供了一套机制来控制这些行为。
以下是Java内存模型的几个主要组成部分:
主内存与工作内存
在java中,所有实例域、静态域和数组元素存储在堆内存中,堆内存在线程之间共享。局部变量(Local variables),方法定义参数(java语言规范称之为formal method parameters)和异常处理器参数(exception handler parameters)不会在线程之间共享,它们不会有内存可见性问题,也不受内存模型的影响。
Java线程之间的通信由Java内存模型(简称为JMM)控制,JMM决定一个线程对共享变量的写入何时对另一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。Java内存模型的抽象示意图如下:
线程A与线程B之间如要通信的话,必须要经历下面2个步骤:首先,线程A把本地内存A中更新过的共享变量刷新到主内存中去。然后,线程B到主内存中去读取线程A之前已更新过的共享变量。
如上图所示,本地内存A和B有主内存中共享变量x的副本。假设初始时,这三个内存中的x值都为0。线程A在执行时,把更新后的x值(假设值为1)临时存放在自己的本地内存A中。当线程A和线程B需要通信时,线程A首先会把自己本地内存中修改后的x值刷新到主内存中,此时主内存中的x值变为了1。随后,线程B到主内存中去读取线程A更新后的x值,此时线程B的本地内存的x值也变为了1。
从整体来看,这两个步骤实质上是线程A在向线程B发送消息,而且这个通信过程必须要经过主内存。JMM通过控制主内存与每个线程的本地内存之间的交互,来为java程序员提供内存可见性保证。
内存可见性
为了保证不同线程之间对共享变量操作的可见性,JMM提供了同步机制。当一个线程修改了共享变量的值,其它线程能够立即得知这个修改。常用的同步机制有:
1、synchronized:一个基于进入和退出监视器(monitor)的同步块。
2、volatile:一个轻量级的同步机制,可以确保变量的修改对所有线程立即可见,以及禁止指令重排序。
禁止重排序
JMM允许编译器和处理器对操作顺序进行重排序,以提高性能,但是在某些关键区域,JMM禁止这种重排序以保证代码的执行结果符合预期。通过插入特定类型的内存屏障(Memory Barriers),JMM能够在必要的时候阻止重排序。
happens-before
JMM把 happens-before 要求禁止的重排序分为了下面两类:
1、会改变程序执行结果的重排序。
2、不会改变程序执行结果的重排序。
JMM对这两种不同性质的重排序,采取了不同的策略,如下:
1、对于会改变程序执行结果的重排序,JMM要求编译器和处理器必须禁止这种重排序。
2、对于不会改变程序执行结果的重排序,JMM对编译器和处理器不做要求(JMM允许这种重排序)。
对happens-before关系的定义
1)如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。
2)两个操作之间存在happens-before关系,并不意味着Java平台的具体实现必须要按照happens-before关系指定的顺序来执行。如果重排序之后的执行结果,与按happens-before关系来执行的结果一致,那么这种重排序并不非法(也就是说,JMM允许这种重排序)。
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。
5)start()规则:如果线程A执行操作ThreadB.start()(启动线程B),那么A线程的ThreadB.start()操作happens-before于线程B中的任意操作。
6)join()规则:如果线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作happens-before于线程A从ThreadB.join()操作成功返回。
本文作者:whitebear
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!