🌈🌈🌈🌈🌈🌈🌈🌈
欢迎关注公众号(通过文章导读关注:【11来了】),及时收到 AI 前沿项目工具及新技术的推送!在我后台回复 「资料」 可领取
编程高频电子书
!
在我后台回复「面试」可领取硬核面试笔记
!文章导读地址:点击查看文章导读!
感谢你的关注!
🍁🍁🍁🍁🍁🍁🍁🍁
学习内存屏障注意事项:
对于内存屏障的内容不要太抠细节,因为对于不同的底层硬件,内存屏障的实现也是不同的,所以在学习的时候,有些文章中是加这个屏障,而另外一些文章又是加其他的屏障,这个都无所谓的,我们只
需要学习到内存屏障是如何保证可见性和有序性的就可以了
这里主要聊一聊 synchronized 底层到底是如何保证原子性、可见性和有序性的
原子性
的保证这里保证的原子性就是当一个线程执行到 synchronized 的同步代码块中时,不会在执行过程中被其他线程中断
synchronized 是基于两个 JVM 指令来实现的:monitorenter
和 monitorexit
那么在这两个 JVM 指令中的代码就是被上了锁的,这一段代码就只有当前加锁的线程可以执行,从而保证原子性
可见性
的保证通过添加一些 内存屏障
来保证,在 synchronized 修饰的同步代码块中所做的 所有变量写操作
,都会在释放锁的时候,强制执行 flush 操作
,来保证可以让其他处理器中的线程可以感知到变量的更新
而在进入 synchronized 的同步代码块时,会先执行 refresh 操作
,来保证读取到最新变量
有序性
的保证也是通过加各种 内存屏障
来保证的,避免指令重排的问题
接下来看一下,在 synchronized 同步代码块中,到底会添加哪些 内存屏障
:
int b = 0;
int c = 0;
synchronized (this) { --> monitorenter
--> Load 内存屏障
--> Acquire 内存屏障
int a = b;
c = 1;
--> Release 内存屏障
} --> monitorexit
--> Store 内存屏障
这里可能大家对 Acquire
和 Release
内存屏障有点陌生,但是一定知道 LoadLoad、LoadStore、StoreStore、StoreLoad 屏障,下边说一下他们的关系:
Acquire 屏障 = LoadLoad + LoadStore
Release 屏障 = LoadStore + StoreStore
那么对于上边 synchronized 的同步代码块,这里解释一下每个屏障的作用:
monitorenter
指令后,添加 Load 屏障
,执行 refresh 操作,可以去将其他处理器中修改过的最新数据加载到自己的高速缓存种Acquire 屏障
,可以保证当前线程可以读到 Acquire 屏障前所有内存操作的结果monitorexit
指令前,添加 Release 屏障
,保证一个线程在执行到屏障之后的内存操作之前,其他线程能看到该线程在屏障之前的所有内存操作的结果monitorexit
指令后,添加了 Store 屏障
,对自己在同步代码块中修改的变量执行 flush 操作,刷新到高速缓存或者=主内存中,让其他处理器中的线程可以感知到数据的变化因此通过 Acquire、Release、Load、Store 屏障来保证了有序性
一句话总结
简单总结一下就是,在 synchronized 代码块开始时,加内存屏障,保证可以感知到屏障前所有的内存操作变化,在 synchronized 结束后,加一个内存屏障,保证可以将内存操作的更新情况立即刷新到高速缓存或者主内存中,可以让其他线程感知到!
volatile 是不保证原子性的,只保证了可见性和有序性,底层就是基于各种 内存屏障
来实现的
使用 volatile 关键字之后,加入的内存屏障如下:
volatile boolean flag = false;
--> Release 屏障
flag = true; // volatile 写
--> Store 屏障
--> Load 屏障
if (flag) { // volatile 读
--> Acquire 屏障
// ...
}
主要是在 volatile 写操作和读操作前后都添加内存屏障来保证:
在 volatile 写操作之前
,加入了 Release 屏障,保证了 volatile 写和 Release 屏障之前的任何读写操作不会发生指令重排在 volatile 写操作之后
,加入了 Store 屏障,保证了写完数据之后,立马会执行 flush 操作,让其他处理器的线程感知到数据的更新在 volatile 读操作之前
,加入了 Load 屏障,保证可以读取到这个变量的最新数据,如果这个变量被其他处理器中的线程修改了,必须从其他处理器的高速缓存或者主内存中加载到自己本地高速缓存里,保证读到的是最新数据在 volatile 读操作之后
,加入了 Acquire 屏障,禁止 volatile 读操作之后的任何读写操作
和 volatile 读操作
发生指令重排