1、 为什么要有Java内存模型?
1.1、 CPU和缓存一致性
1. 缓存一致性问题出现的原因
CPU的执行速度和内存的读取速度差距越来越大,导致CPU每次操作内存都要耗费很多等待时间。为解决这个问题,早期的程序员大佬提出了 “CPU和物理内存上新增高速缓存” 。 将运算所需要的数据从主内存复制一份到CPU的高速缓存 中,当 CPU进行计算时就可以直接从高速缓存中读数据和写数据 了,当运算结束再将数据刷新到主内存就可以了。 在多核 CPU和多线程的情形 中,每个线程都有自己的缓存,关于同一个线程共享数据的缓存内容可能不一致 。
1.2、 处理器优化和指令重排
1. 处理器优化
为了 使处理器内部的运算单元能够被充分利用 , 处理器可能会对程序代码进行乱序执行处理 ,这就是 处理器优化 。
2. 指令重排
除了现在很多流行的处理器 会对代码进行优化乱序处理 ,很多编程语言的编译器也会有类似的优化,比如 Java 虚拟机的即时编译器(JIT)也会做指令重排 。
1.3、 并发编程带来的问题
1. 三大问题
原子性问题,可见性问题和有序性问题 。其实 就是上面讲的『缓存一致性』、『处理器优化』、『指令重排序』造成的 。
2. 并发编程保证数据安全需要满足的特性
- 原子性 :指的是在一个操作中CPU 不可以在中途暂停然后再调度, 要么不执行,要么就执行完成 。
- 可见性 :指的是 多个线程访问同一个变量 时,一个线程修改了这个变量的值, 其他线程能够立即看得到修改后的值 。
- 有序性 :指的是 程序执行的顺序按照代码的先后顺序执行 ,而不能瞎几把重排,导致程序出现不一致的结果。
1.4、 JMM诞生的原因
Java 为了保证并发编程中可以满足原子性、可见性及有序性 ,诞生出了一个重要的概念,那就是 Java内存模型 ,内存模型 定义了共享内存系统中多线程程序读写操作行为的规范 。通过这些规则来规范对内存的读写操作,从而保证指令执行的正确性, 它解决了 CPU 多级缓存、处理器优化、指令重排等导致的内存访问问题 , 保证了并发场景下的一致性、原子性和有序性 。
- JMM 内存模型解决并发问题主要采用两种方式: 限制处理器优化 和 使用内存屏障 。
2、Java内存模型
2.1、JMM对内存的划分
1. JMM对内存的划分和工作运作规则
JMM规定了内存主要划分为 主内存和工作内存 两种。Java内存模型规定了 所有的变量都存储在主内存中 (此处的主内存与介绍物理硬件时提到的主内存名字一样,两者也可以类比,但物理上它仅是虚拟机内存的一部分)。每条线程还有自己的 工作内存(可与前面讲的处理器高速缓存类比) ,线程的工作内存中