目录:
- 计算机硬件系统架构演进(缓存一致性问题)
- 为何演进,如何演进
- 演进后导致了什么问题
- 如何解决
- JVM如何解决缓存一致性问题
- 如何解决
- volatile内存语意含义
- volatile原理
计算机硬件系统架构演进(缓存一致性问题)
1、为何演进,如何演进。
计算机在运行程序的时,每条指令都是在CPU中执行,在执行的过程中势必会涉及到数据的读写。
而程序运行的数据时存储在主内存中的,也就是我们常说的内存,那么此时就会有一个问题:
- CPU执行速度过快,内存执行速度较之较慢。
- 若任何交互都需要与主内存打交道的话,这会大大降低CPU执行的效率。
解决办法:
- CPU高速缓存诞生。
- CPU高速缓存只为某个CPU独有,只与在该CPU运行的线程有关。
这样CPU不必与执行速度较之较慢的主内存打交道,而是和运行速度快的CPU高速缓存打交道,这样充分利用了CPU的高效性能。
———————————————————————————————————————————————————————
2、演进后导致了什么问题,如何解决的
硬件的演进使用了CPU高速缓存,这在单线程中的确是没问题的,但在多线程中便会导致缓存一致性的问题。
如i++;这段代码:
- 先从主内存中取出i的值。
- 然后复制一份到CPU高速缓存中,CPU执行+1操作。
- 再然后将+1后的结果写回CPU高速缓存。
- 最后刷新到主内存中。
此时有两个线程都这样操作,那预期i的值应该是3。
两个线程并发操作,会导致如下结果:
- 两个线程分别从主内存中读取i的值到各自的高速缓存中。
- 此时线程A先执行完,将结果2更新到主内存中。
- 然后线程B后执行完,又将主内存中的值更新为2.
- 所有当A、B两个线程执行完后,结果并不为3,而是2。
———————————————————————————————————————————————————————
3、如何解决
- 主线上加锁。
- 缓存一致性协议。
加锁的方式通过独占来实现,只有一个CPU能执行,效率极为低下,不推荐使用。所以一般会采用缓存一致性协议来实现,【窥探技术+MESI协议】。
窥探技术:
- 既然多核CPU发生缓存一致性的问题是不能共享主内存拿到的数据,那么我就共享数据。
- 将所有内存传输都放到一条共享总线上,所有CPU都能看到这条总线上的数据。
- 并且谁修改了后都通知其它CPU。
MESI协议:
- M:modify(修改),E:exclusive(独占),S:shared(共享),I:invalid(失效)。
- 当缓存处于E或M时,CPU才能去写。
- 如果CPU要执行写操作的话,需要向总线发送一条我要独占的请求,此时会通知其它CPU,你们的缓存是I(invalid)状态了。
JVM如何解决缓存一致性问题
因JVM是硬件层次之上的,所以需要解决缓存一致性问题。
1、如何解决:通过volatile关键字。
- volatile的作用是保证共享变量的可见性,不能保证原子性,也不能保证线程安全。
- volatile的作用是确保所有线程在同一时刻读取到的共享变量的值是一致的。
- 如果某个线程对volatile修饰的共享变量进行更新,那么其他线程可以立刻看到这个更新。
———————————————————————————————————————————————————————
2、volatile内存语意含义
volatile可以保证线程可见性且提供了一定的有序性,但是无法保证原子性。
在JVM底层volatile是采用内存屏障来实现的。
上面那段话,有两层语义
- 保证可见性、不保证原子性(再次重声,仅仅保证可见性)。
- 禁止指令重排序。
———————————————————————————————————————————————————————
3、volatile原理
JVM底层是通过一个叫做内存屏障的东西来完成。
内存屏障,也叫做内存栅栏,是一组处理器指令,用于实现对内存操作的顺序限制。
下面是完成上述规要求的一些规则,在NO的地方就是需要用内存屏障来控制的。
也就是第一个操作以volatile读开始的都要经过内存屏障,第二个操作是volatile写的时候都要经过内存屏障;第一个是volatile写, 第二个是volatile读也需要经过内存屏障。
读:Load,写:Store;再配合上图就很好记了。