JMM
本文最后更新于 2025年5月20日 15:03
Java 内存模型(Java Memory Model,JMM)定义了 Java 程序中的变量、线程如何和主存以及工作内存进行交互的规则,主要涉及到多线程环境下的共享变量可见性、指令重排等问题。Java通过共享内存并发模型来解决不同线程之间的通信和同步问题。Java 线程之间的通信由 Java 内存模型(简称 JMM)控制。
共享变量
由图可见,如果线程A想要和线程B进行通信,则要经过以下两个步骤:首先,线程A将其本地内存中更新过的共享变量的副本写入主内存对应的共享变量中;然后线程B从主内存中的共享变量读取线程A写入的值。所以可见,线程之间无法直接访问彼此的工作内存,线程之间的通信必须要经过主内存。
线程 B 并不是直接去主存中读取共享变量的值,而是先在本地内存 B 中找到这个共享变量,发现这个共享变量已经被更新了,然后本地内存 B 去主存中读取这个共享变量的新值,并拷贝到本地内存 B 中,最后线程 B 再读取本地内存 B 中的新值。
内存可见性
那么怎么知道这个共享变量的被其他线程更新了呢?这就是 JMM 的功劳了,也是 JMM 存在的必要性之一。JMM 通过控制主存与每个线程的本地内存之间的交互,来提供内存可见性保证。
在 Java 中,volatile
关键字保证了 内存可见性,即当一个线程更新 volatile
变量时,其他线程可以立即看到该更新的值。Java 中的volatile
关键字可以保证多线程操作共享变量的可见性以及禁止指令重排序,syncronized
关键字不仅保证可见性,同时也保证了原子性(互斥性)。在更底层,JMM 通过内存屏障来实现内存的可见性以及禁止重排序。
JMM 与 Java 运行时内存区域 的区别
Java 运行时内存区域描述的是在 JVM 运行时,如何将内存划分为不同的区域,并且每个区域的功能和工作机制。
Java 内存模型 (JMM) 主要针对的是多线程环境下,如何在主内存与工作内存之间安全地执行操作。
JMM与指令重排序
指令重排对于提高 CPU 性能十分必要,但也带来了乱序的问题。指令重排可以保证串行语义一致,但是没有义务保证多线程间的语义也一致。所以在多线程下,指令重排序可能会导致一些问题。
Java 内存模型(JMM)对于正确同步多线程程序的内存一致性做了以下保证:如果程序是正确同步的,程序的执行将具有顺序一致性。即程序的执行结果和该程序在顺序一致性模型中执行的结果相同。这里的同步包括使用volatile
关键字、syncronized
关键字等。
顺序一致性模型
- 一个线程中的所有操作必须按照程序的顺序(即 Java 代码的顺序)来执行。
- 不管程序是否同步,所有线程都只能看到一个单一的操作执行顺序。即在顺序一致性模型中,每个操作必须是原子性的,且立刻对所有线程可见。
JMM并不保证顺序一致性
对于顺序一致性模型的第一个特点:JMM中,临界区内的代码可以在不违反同步规则的前提下发生重排序。这种重排序提高了程序的运行效率,又没有改变程序的执行结果。
对于顺序一致性模型的第二个特点:当前线程首先把数据写入到本地内存的副本共享变量中。在没有刷新到主内存之前,这个写操作仅对当前线程可见;在其他线程看来,这个写操作根本就没有被当前线程执行。
happens-before
JMM 使用 happens-before 的概念来定制两个操作之间的执行顺序。这两个操作可以在一个线程内,也可以是不同的线程中。
- 如果一个操作 happens-before 另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。
- 两个操作之间存在 happens-before 关系,并不意味着 Java 平台的具体实现必须要按照 happens-before 关系指定的顺序来执行。如果重排序之后的执行结果,与按 happens-before 关系来执行的结果一致,那么 JMM 也允许这样的重排序。
在 Java 中,有以下天然的 happens-before 关系:
- 程序顺序规则:一个线程中的每一个操作,happens-before 于该线程中的任意后续操作。
- 监视器锁规则:对一个锁的解锁,happens-before 于随后对这个锁的加锁。
- volatile 变量规则:对一个 volatile 域的写,happens-before 于任意后续对这个 volatile 域的读。
- 传递性:如果 A happens-before B,且 B happens-before C,那么 A happens-before C。
- start 规则:如果线程 A 执行操作
ThreadB.start()
启动线程 B,那么 A 线程的ThreadB.start()
操作 happens-before 于线程 B 中的任意操作。 - join 规则:如果线程 A 执行操作
ThreadB.join()
并成功返回,那么线程 B 中的任意操作 happens-before 于线程 A 从ThreadB.join()
操作成功返回。
总的来说,JMM禁止会改变程序执行结果的重排序,但允许不会改变程序执行结果的重排序。