首先放总结图:
一、问题引入:
Java中广泛使用的CAS技术底层源码都是使用的Unsafe类,而这是一个native方法,本质上绕过了JVM,是由C/C++语言编写的,直接与操作系统底层相关,所以可以实现操作的原子性。
那么操作系统底层是如何保证操作的数据一致性、有序性、原子性的呢?
- 一致性:总线窥探原理 + 缓存一致性协议(硬件层面)
- 有序性:解决指令重排问题,内存屏障(软件层面,使用barrier编译器告诉计算机要按照顺序执行这段指令)
- 原子性:缓存一致性协议
二、cpu处理数据的过程:
每个CPU都和主线连接,每个缓存、内存之间的相互通信都要经过总线进行。一般CPU执行指令的顺序如下:
1. CPU从磁盘读取程序指令和数据到内存(拿到数据存储的地址的指针)
2. 加载指令和数据到CPU的缓存
3. CPU执行指令,将结果写入缓存
4. 将缓存中的结果数据写回内存
三、如何保证一致性和原子性:
不同CPU连接的内存可以保存同一个变量,不加限制的话就会存在并发问题,所以当某个CPU要改变某个共享数据时,会先通过总线告知其他CPU不可使用该数据,收到其他CPU的确认信号后,这个CPU再开始对这个数据的修改.(MESI协议)。
注:此处会有指令重排问题:
等待其他CPU的ACK信号会耗时,所以这里会有指令重排的问题,也就是CPU优化了指令的执行顺序,要修改共享数据时,不会阻塞等待其他CPU的响应,而是先往下执行对非共享数据操作的指令,收到其他CPU的ACK信号以后再反过来执行之前的指令。
所以以上指令重排优化过程会导致以下的指令执行顺序性问题,并发情况下会出现数据计算结果异常。
四、如何保证指令执行顺序性:
使用barrier编译器(内存屏障,软件层面防止指令重排)编译指令,计算机底层会取消指令重排优化,强制按照程序员规定的顺序执行指令。