Java 内存模型
网上关于 Java 内存模型的内容特别多,很多都讲到了多 CPU 与缓存的数据一致性问题,于是顺带牵出了 MESI 等缓存一致性协议。其实到这里都没问题,都挺有逻辑的。
但接下来为啥有 Java 内存模型?为啥又有 happens-before 原则?这些内容基本上没有一个说得清楚,这就让人很困惑了。此外,有些还扯出了内存屏障、执行时序的问题,但都没啥逻辑,听起来乱糟糟的。
对于 Java 内存模型,舍弃了一些不必要的细碎点,整理了一些理解,相对来说还是比较好理解的。
首先,由于多核 CPU 和高速缓存在存在,导致了缓存一致性问题。 这个问题属于硬件层面上的问题,而解决办法是各种缓存一致性协议。不同 CPU 采用的协议不同,MESI 是最经典的一个缓存一致性协议。
其次,操作系统作为对底层硬件的抽象,自然也需要解决 CPU 高速缓存与内存之间的缓存一致性问题。 各个操作系统都对 CPU 高速缓存与缓存的读写访问过程进行抽象,最终得到的一个东西就是「内存模型」。
从硬件到操作系统,这个是自己的理解,并没有找到一些资料提到这点。但这应该是没有错的。因为操作系统就是对底层硬件的抽象,而所有抽象的东西就需要定义一些概念。
对于操作系统来说,这些概念就是内存模型、CPU 时间片等。内存模型这个词,在操作系统的教科书上也是可以找到的,这也是一个佐证吧。
于是,从硬件层面理解到了操作系统层面,但这跟 Java 内存模型有啥关系呢?
最后,Java 语言作为运行在操作系统层面的高级语言,为了解决多平台运行的问题,在操作系统基础上进一步抽象,得到了 Java 语言层面上的内存模型,其也是为了解决多线程情况下的数据一致性问题。
因为要实现 Java 语言的「Write Once, Run Anywhere」的理念,那么就必须解决多平台内存模型不一致的问题,这样才创造出了 Java 内存模型。
Java 内存模型规定了很多规则,如果 Java 程序能够遵守 Java 内存模型的规则,那么其写出的程序就是并发安全的,这就是 Java 内存模型最大的价值。
到这里,从硬件、操作系统再到语言层面,知道了 Java 内存模型诞生的原因,知道其诞生就是为了解决多平台的内存模型统一问题,进一步其实就是多线程的数据一致性问题。
happens-before 原则
前面说到,为了解决多平台的内存模型统一,以及多线程的数据一致性问题,所以有了 Java 内存模型。但是 Java 内存模型的内容太多了,基本就记不住,非常不利于编程人员理解,所以才有了 happens-before 原则。
所以说 happens-before 原则是对 Java 内存模型的简化,才能更好地写出并发代码。
volatile
关键字
volatile
关键字,其实也与 Java 内存模型有关系,只是很多文章都没说清楚。volatile
关键字有两个作用,就是可见性和禁止指令重排序。但为啥它有这两个作用呢?其实 volatile 这两个作用的来源,就来自于 Java 内存模型里对 volatile 变量定义的特殊规则。
这就是 volatile 关键字与 Java 内存模型的关系,比较简单。
至于内存屏障这个词,其实就是一个方便理解的名词,诞生于 volatile 禁止指令重排序这个作用里,也没啥不好理解的。
synchronized
关键字
**synchronized**
关键字,也是并发编程常用到的内容,其实它和 Java 内存模型没关系,但和 Java 虚拟机规范有关系。synchronized
关键字经过编译之后,会在同步块的前后分别形成 monitorenter 和 monitorexit 这两个字节码指令,这两个字节码的执行需要指明一个要锁定或解锁的对象。
而 monitorenter
和 monitorexit
这两个字节码指令为啥能实现这样的功能,是因为 Java 虚拟机中做了强制定义,那么虚拟机就需要实现。**synchronized**
关键字与 Java 对象的内存布局,也是有关系的。自旋锁、自适应锁、偏向锁,它们靠什么实现,就是 Java 对象中的对象头去判断,然后进行一系列的逻辑操作。
总结
至此,基本上可以把 Java 并发编程里常见的那些概念的关系搞清楚了。
Java 内存模型 是对内存布局的抽象,解决多平台运行以及多线程一致性的问题。 happens-before 原则 是 Java 内存模型定义的简化,方便学习。 **volatile**
则是轻量级同步同步机制,其来源于 Java 内存模型赋予的权利。**synchronized**
关键字的合法性,则来自于 Java 虚拟机规范。而 synchronized 中自旋锁、自适应锁、偏向锁等,都依靠 Java 对象的对象头 来判断。