Java并发编程问题汇总一:Java内存模型(JMM)

Page content

Java多线程编程,是Java并发编程的主要手段,多线程编程中,变量的共享和同步问题,又是需要我们地一个迈过的门槛,所以了解Java内存模型,是了解Java并发编程的第一步。

为什么需要内存模型?

为什么需要内存模型,CPU操作一个内存中的值,内存中的值随之改变,所有的线程都可以立刻感受到内容的变化,不香吗?如果不考虑性能问题,这样做是可行的,但是现实比较参考,RAM的速度与CPU寄存器的速度差距巨大,RAM速度在程序运行过程中,是巨大的瓶颈。如果按照上面所说的方式来实现,CPU性能根本不需要想现在这样强大,说不定一个几十年前的CPU就足够配合目前的RAM来运行程序了,但这样效率无法满足目前世界对速度的需求。另一方面,从成本和技术上考量,RAM不可能做的更快了。

为了填补寄存器与内存之间的巨大速度差距,选择不直接与内存进行交互,而是利用了一个计算机领域一个普遍存在的现象:缓存。

实际情况中,数据的访问存在两个特点:

  1. 时间局部性:刚写入、访问的数据,更容易被接下来访问到。
  2. 空间据不行:一个数据被访问到,他周围的数据也更容易被访问到。

因为这两个特性,缓存技术应运而生。

现代CPU使用了三级缓存:L1、L2、L3,他们之间的速度是依次减小的,容量是依次增大的。其中L1速度最快,直接与寄存器交互,L2速度次之,与L1交互,如此类推。通过缓存技术,CPU与内存之间的速度差距被进一步填补。

JVM与CPU缓存模型

JVM中的线程与系统线程一一绑定。为了屏蔽系统的实现差别,JVM抽象出了自己的一套内存模型。一般而言,线程工作内存一般优先存在高速缓存中,主内存就是物理内存。

20200727131202

Java的三个概念以及JMM的保证

原子性、有序性、可见性是三个重要概念:

  1. 原子性:对基本数据类型的变量的读取和赋值操作是原子性操作,即这些操作是不可被中断的,要么执行,要么不执行。JMM只保证了读取和复制的原子性,更复杂的原子性保证需要用到锁。
  2. 有序性:java内存模型中,允许编译器和处理器对指令进行重排序。volatile关键字可以保证一定程度的有序行,使用锁可以完全保证有序性。
  3. 可见行:当一个变量用volatile修饰,那么对他的修改可以被其他线程立刻感知。

但是如果有序性的保证只能靠volatile和锁,那么写程序会变得非常麻烦,所以JMM天然保证了一定有序性:

  1. 程序顺序原则:单线程顺序。
  2. 监视器原则
  3. volatile域原则
  4. 传递性原则

double和long型的特殊规则

因为double和long都是64位,而Java一次只能操作32位,所以对double和long型操作是64位,所以分两次操作。但目前主流的商业的虚拟机都是当作原子实现的,所以一般不用加volatile。

参考

Java 内存模型详解