跳至主要內容

对于 JMM 的理解

chenxi编程并发编程大约 3 分钟

很抽象的一个知识点,但正因如此才要好好的总结。

先聊聊 CPU Cache

我们都知道在 CPU 中有着缓存,目的是为了解决 CPU 处理速度与内存不匹配的问题。

CPU 有了 Cache 的存在,其在工作时,即可将内存中的数据拷贝到 Cahce 中,CPU 需要使用时即可从 Cache 中读取数据,这样就大大提高了 CPU 的工作效率。
但同时,在多线程的情况下,这也可能导致并发问题的发生,例如,两个线程同时从内存中读取数据 i,其值为 1,两个线程同时执行i++的操作,执行完毕后,将数据写回内存,此时内存中的数据 i 的值为 2,但我们期望的值应为 3。

CPU 为了解决这样的并发问题,可以通过制定 缓存一致性协议(MESI) 来解决,该协议的核心思想就是,当 CPU 修改了缓存中的数据,就会立即将该数据写回内存中

为什么要有 JMM

既然通过缓存一致性协议可以对并发问题进行解决,那还需要 JMM 干嘛?

要知道缓存一致性协议是与底层硬件紧密相关,而我们的程序都是运行在操作系统之上,而操作系统为了隐藏底层硬件的细节,提出了 内存模型(Memory Model) 来解决这个问题,内存模型可以理解为是一种规范,无论是 Windows 系统,还是 Linux 系统,它们都有特定的内存模型。

而对于 Java 来说,因为 Java 的一大特点就是跨平台,为了屏蔽不同系统内存模型不同的差异,所以提出了 JMM(Java Memory Model)

什么是 JMM

翻译过来就是 Java 的内存模型,个人理解就是通过制定一系列规范,解决了 Java 语言编写的程序中存在的并发问题。
比较关键的点就是,线程有着自己独立的工作内存,不同线程间对于工作内存是相互隔离的,只能通过主内存进行交互。

还是画个图吧:

更具体的说,JMM 规定了线程的工作内存如何与主内存进行交互。

而对于我们开发人员来说,只需要在编码时,合理使用如volatilesynchronized这类与多线程相关的关键字来进行开发即可有效避免并发问题。

多线程下的可见性问题

有了对 JMM 的了解,再回头看多线程中的可见性问题,就不难理解了。

public class MyThread {
    private static boolean flag = false;

    public static void main(String[] args) {
        // 线程1
        new Thread(() -> {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            flag = true;
        }).start();

        // 线程2
        new Thread(() -> {
            while (!flag) {
            }
            System.out.println("循环成功终止!"); 
        }).start();
    }
}

在这段程序中,通过线程 1 修改共享变量flag的值为ture,那按理来说,线程 2 将会停止循环,并打印语句。但实际上,线程 2 会一直处于循环当中,也就是说线程 2 无法感知到线程 1 对共享变量的修改。
从 JMM 的角度来说,就是因为线程 2 其工作内存中的flag变量仍然是最初从主内存读到的,没有进行更新。
而要解决这个问题,往往会通过在共享变量前添加关键字volatile来解决,这就指示 JVM,这个变量是共享且不稳定的,每次使用它都到主存中进行读取。

上次编辑于:
贡献者: chenxi