手机网站的建设,高端网站建设浩森宇特,文化创意有限公司,临夏州建设网站#x1f308;#x1f308;#x1f308;#x1f308;#x1f308;#x1f308;#x1f308;#x1f308; 欢迎关注公众号#xff08;通过文章导读关注#xff1a;【11来了】#xff09;#xff0c;及时收到 AI 前沿项目工具及新技术的推送#xff01; 在我后台回复… 欢迎关注公众号通过文章导读关注【11来了】及时收到 AI 前沿项目工具及新技术的推送 在我后台回复 「资料」 可领取编程高频电子书 在我后台回复「面试」可领取硬核面试笔记 文章导读地址点击查看文章导读 感谢你的关注 硬件级别可见性问题面试实战
这里为什么要了解一下可见性的底层原理呢
因为对于可见性这块的内容他并不是软件层面上的问题而是硬件层面的问题是底层的一些机制导致了可见性的问题了解了底层的相关内容之后我们的知识会更容易形成一个闭环而不仅仅是停留于软件层面对下层一无所知
所以接下来聊一聊底层中到底是什么原因导致了不同线程之间出现这个可见性的问题
可见性硬件级别造成原因
首先每一个处理器都有自己的寄存器而线程对变量的读操作都是针对写缓冲进行的因此这个可见性问题与 寄存器 和 写缓冲 这两个硬件组件是有关联的
这里分别说一下寄存器和写缓冲 如何导致了可见性的问题 多个处理器都在运行各自的线程的时候如果其中一个处理器中的线程将某一个变量更新后的值放在寄存器中那么其他处理器中的线程是没有办法看到这个更新后的值的因为这个寄存器是各个处理器私有的因此寄存器会导致可见性的问题 处理器运行的线程对变量的写操作是针对写缓冲进行的之后才会刷到主内存中因此如果一个线程更新了变量如果仅仅写入到了写缓冲充还没有刷到主内存或高速缓存中那么其他处理器中的线程是无法感知到这个变量的修改的此时导致可见性的问题 即使这个写缓冲的数据的更新也同步到了自己的主内存或高速缓存里并且将这个更新通知给了其他的处理器但是其他处理器可能把这个更新放到无效队列中并没有更新自己的高速缓存此时仍然会导致可见性的问题
如下这个图
MESI 协议
那么要实现多个处理器的共享数据的一致性可以通过 MESI 协议来实现
根据具体底层硬件的不同MESI 协议的具体实现也是不同的
这里说一种 MESI 协议的实现通过将其他处理器高速缓存中 更新后的数据 拿到自己的高速缓存中更新一下这样不同处理器之间的高速缓存中的数据就保持一致了实现了可见性
在实现 MESI 协议的过程中需要 两个关键的机制 来确保缓存的一致性flush 和 refresh
flush
将自己更新的值刷新到高速缓存里去让其他处理器在后续可以通过一些机制从自己的高速缓存里读到更新后的值
并且还会给其他处理器发送一个 flush 消息让其他处理器将对应的缓存行标记为无效确保其他处理器不会读到这个变量的过时版本
refresh
处理器中的线程在读取一个变量的值的时候如果发现其他处理器的线程更新了变量的值必须从其他处理器的高速缓存或者是主内存里读取这个最新的值更新到自己的高速缓存中
因此在底层通过 MESI 协议、flush 处理器缓存和 refresh 处理器缓存来保证可见性的
总结一下就是flush 是强制将更新后的数据从写缓冲器中刷新到高速缓存中去refresh 是去感知到其他处理器更新了变量主动从主内存或其他处理器的高速缓存中加载最新数据
那么举个例子对于 volatile 变量来说
volatile boolean flag true;当写 volatile 变量时就会通过执行一个内存屏障在底层会触发flush处理器缓存的操作把数据刷到主内存中
当读 volatile 变量时也会通过执行一个内存屏障在底层触发refresh操作从主内存中读取最新的值
指令重排
指令重排的内容我们可以来了解一下什么时候会发生指令重排
指令重排指的是我们写好的代码在真正执行的时候执行顺序可能会被重排序如果重排序之后在多线程的执行环境下可能就会出现一些问题
什么时候会发生指令重排呢
编译期间
Java 中有两种编译器一种是静态编译器javac另一种是动态编译器JIT
javac 负责把 .java 文件中的源代码编译为 .class 文件中的字节码这个一般是程序写好之后进行编译的
JIT 是 JVM 的一部分负责把 .class 文件中的字节码编译为 JVM 所在操作系统支持的机器码一般在程序运行过程中进行编译
那么在编译期间可能编译器为了提高代码的执行效率会对指令进行重排JIT 对指令重排还是比较多的
处理器执行顺序
编译器编译好的指令到真正处理器执行的时候可能还会调整顺序
指令重排有什么规则约束呢
上边讲过了一个 happens-before 原则它定义了一些规则只要符合 happens-before 中的规则的都不会进行指令重排
就比如说下边代码的第三行不可能重排到上边因为它的执行结果依赖了上边两行的执行结果因此不会重排但是前两行可能会重排
int a 1;
int b 2;
int c a b;