JMM——软硬件基础

JMM——软硬件基础

一、硬件基础

前置知识:计算机基础——CPU速度比内存快100倍,比磁盘快1000000倍
在这里插入图片描述

在上面的模型图中我们知道,每个CPU内部有L1、L2两个缓存,那么当内存中的数据被load到每个CPU的缓存中,当其中一个CPU修改了数据,另一个CPU如何对数据进行即时同步(可见性),保证数据一致性?

在这里插入图片描述

1、总线锁

在这里插入图片描述

由于总线锁造成效率很低,很老的CPU才会使用总线锁,现在很少单独使用总线锁了

2、MESI——缓存一致性协议

缓存一致性协议有很多,不同的硬件厂商都有各自的实现

Intel使用的MESI最广范,我们平常一般来讲都是说的MESI:https://www.cnblogs.com/z00377750/p/9180644.html

在这里插入图片描述

MESI——缓存的四种状态,以及对这四种状态的不同处理

在有缓存锁的情况下,还需要使用总线锁吗?

缓存的效率很高,但是缓存容量不大,如果有很大的数据需要缓存一致性,但是不能放入缓存,此时就需要使用总线锁

现代CPU的数据一致性实现 = 缓存锁(MESI …) + 总线锁

3、缓存行

当把内存中的数据读到缓存中,并不是说用到哪一个字节就只Load哪一个字节,而是一次读取一组数据(时间局部性和空间局部性??),这组数据称为缓存行

目前大多数的缓存行的实现大小都是64字节

伪共享

位于同一缓存行的两个不同数据,被两个不同CPU锁定,产生互相影响、不断更新的伪共享问题,造成效率浪费

缓存行对齐

在缓存行前面和后面填充占位字节,让目标数据要么在上一个缓存行,要么在下一个缓存行,而不会同时存在于两个缓存行——类似于用空间换时间(提高执行效率)

此时可以使用缓存行的对齐能够提高效率,比如并发中的Disruptor(环形buffer),里面的cursor(游标),就使用了缓存行对齐:
在这里插入图片描述

二、乱序执行

CPU为了提高指令执行效率,会在一条指令执行过程中(比如这条指令是去内存读数据(慢100倍)),去同时执行另一条指令,前提是两条指令没有先后执行顺序依赖关系

https://www.cnblogs.com/liushaodong/p/4777308.html

在这里插入图片描述

写操作也可以进行合并(了解即可)

https://www.cnblogs.com/liushaodong/p/4777308.html

三、如何保证特定情况下不乱序

我们知道CPU乱序执行的优化,让CPU的运行效率提升了很多,一般情况下也没什么问题

但是对于我们程序员来说,有些情况下(比如高并发),乱序执行有可能会造成数据不一致的情况(比如订单半初始化清零),那么如何在特定情况下保证不乱序呢?

1、硬件层面

有序性保障:CPU内存屏障

Intel的CPU主要有三个指令,即内存屏障

内存屏障前后的指令,不可能发生顺序调换,不允许CPU指令重排,保证有序性

sfence:(save fence)在sfence指令前的写操作当必须在sfence指令后的写操作前完成

lfence:(load fence)在lfence指令前的读操作当必须在ifence指令后的读操作前完成

mfence:(mix fence)在mfence指令前的读写操作当必须在mfence指令后的读写操作前完成

x86计算机架构下的硬件原子指令

原子指令,如x86上的”lock …” 指令是一个Full Barrier,执行时会锁住内存子系统来确保执行顺序,甚至跨多个CPU。Software Locks通常使用了内存屏障或原子指令来实现变量可见性和保持程序顺序

2、JVM级别如何规范(JSR133)

硬件基础上JVM软件级的规范,JVM只是提供了一个规范,不同的虚拟机的实现可能不同

屏障两端互相之间不允许指令交换

LoadLoad屏障:
对于这样的语句Load1; LoadLoad; Load2,

在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。

StoreStore屏障:

对于这样的语句Store1; StoreStore; Store2,

在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。

LoadStore屏障:

对于这样的语句Load1; LoadStore; Store2,

在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。

StoreLoad屏障:
对于这样的语句Store1; StoreLoad; Load2,

​ 在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。

3、Volatile的实现细节

现在有一个趋势——由于Synchronized被不断优化,效率已经比较高了,所以现在的趋势是不再使用volatile,一律使用Synchronized。

1、字节码层面

在源码翻译成字节码之后,查看字节码,发现就是加了ACC_VOLATILE这么一个flag(访问修饰符),当虚拟机读到这个flag的时候,就会在内存区域读写都加内存屏障

在Volatile操作前面加了一个屏障,后面加了一个屏障,保证指令不可以重新排序

2、JVM层面

在JVM层面,对于Volatile内存区的读写前后都加内存屏障,保证指令不能重排序,保证有序性

StoreStoreBarrier

volatile 写操作

StoreLoadBarrier

LoadLoadBarrier

volatile 读操作

LoadStoreBarrier

在这里插入图片描述

4、Synchronized的实现细节

1、字节码层面

在字节码层面,就是通过下面这两个指令:

monitorenter :监视器进入,告诉JVM一个Synchronized块进去了

monitorexit:监视器退出,告诉一个Synchronized块出来了

遇到这两个指令,JVM就知道要进行锁操作

在源码翻译成字节码之后,查看字节码,发现就是加了ACC_SYNCHRONIZED这么一个flag(访问修饰符),当虚拟机读到这个flag的时候,就会在内存区域读写都加内存屏障

在这里插入图片描述

2、JVM层面
C C++ 调用了操作系统提供的同步机制(Linux和Windows实现是不同的)

硬件层面就是使用了 lock 指令


在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_42583242/article/details/107826917