内存模型的相关概念

计算机在执行程序时 每条指令都是在CPU中执行的 而执行指令过程中 势必涉及到数据的读取和写入
由于程序运行过程中的临时数据是存放在主存(物理内存)当中的 这时就存在一个问题
由于CPU执行速度很快 而从内存读取数据和向内存写入数据的过程跟CPU执行指令的速度比起来要慢的多
因此如果任何时候对数据的操作都要通过和内存的交互来进行 会大大降低指令执行的速度 因此在CPU里面就有了高速缓存

也就是 当程序在运行过程中 会将运算需要的数据从主存复制一份到CPU的高速缓存当中
那么CPU进行计算时就可以直接从它的高速缓存读取数据和向其中写入数据
当运算结束之后 再将高速缓存中的数据刷新到主存当中

比如下面的这段代码:
int i = 1;
i = i + 1;
当线程执行这个语句时 会先从主存当中读取i的值 然后复制一份到高速缓存当中
然后CPU执行指令对i进行加1操作 然后将数据写入高速缓存 最后将高速缓存中i最新的值刷新到主存当中

这个代码在单线程中运行是没有任何问题的 但是在多线程中运行就会有问题了
在多核CPU中 每条线程可能运行于不同的CPU中 因此每个线程运行时有自己的高速缓存
(对单核CPU来说 其实也会出现这种问题 只不过是以线程调度的形式来分别执行的)

我们以多核CPU为例
比如同时有2个线程执行这段代码 假如初始时i的值为1 那么我们希望两个线程执行完之后i的值变为3
可能存在下面一种情况:
初始时两个线程分别读取i的值存入各自所在的CPU的高速缓存当中 然后线程A进行加1操作 然后把i的最新值2写入到内存
此时线程B的高速缓存当中i的值还是1 进行加1操作之后 i的值为2 然后线程B把i的值写入内存
所以最终i的值是2 而不是3
这就是著名的缓存一致性问题
如果一个变量在多个CPU中都存有缓存 那么就可能存在缓存不一致的问题
M])}YWX2G$G8~3)G@3KXNSD.png

并发编程中的三个概念

在并发编程中 我们通常会遇到以下三个问题:原子性问题、可见性问题、有序性问题

1 原子性

原子性:即一个操作或者多个操作 要么全部执行 要么就都不执行

2 可见性

可见性是指当多个线程访问同一个变量时 一个线程修改了这个变量的值 其他线程能够立即看得到修改的值

3 有序性

有序性:即程序执行的顺序按照代码的先后顺序执行

  1. int i = 0;
  2. boolean flag = false;
  3. i = 1; //语句1
  4. flag = true; //语句2

上面代码定义了一个int型变量 定义了一个boolean类型变量 然后分别对两个变量进行赋值操作
从代码顺序上看 语句1是在语句2前面的
那么JVM在真正执行这段代码的时候会保证语句1一定会在语句2前面执行吗?不一定
为什么呢?这里可能会发生指令重排序(Instruction Reorder)
什么是指令重排序?
处理器为了提高程序运行效率 可能会对输入代码进行优化
它不保证程序中各个语句的执行先后顺序同代码中的顺序一致 但是它会保证程序最终执行结果和代码顺序执行的结果是一致的.

虽然重排序不会影响单个线程内程序执行的结果 但是多线程呢?
指令重排序不会影响单个线程的执行 但是会影响到线程并发执行的正确性
也就是说 要想并发程序正确地执行 必须要保证原子性、可见性以及有序性
只要有一个没有被保证 就有可能会导致程序运行不正确
内存模型解决并发问题主要采用两种方式**:限制处理器优化使用内存屏障
(处理器优化即指令重排)**