已知:
java
中的synchronized
关键字能保证可见性,有序性,原子性;volatile
关键字能保证可见性,有序性。
问题:
- 为什么
java
中的并发,在硬件层面不能保证,非要在JVM
里处理呢? - 它们在硬件层面是如何对应保证的呢?
java
层面为啥要加这两个关键字才能保证java
的并发特性呢?
指令重排序无法保证有序性
java
中的一行行代码,对应到硬件层面,就是一个个指令,现代处理器为了加快编译速度,有可能会乱序执行指令,这样也就无法保证代码的有序性了。
现代CPU缓存模型无法保证可见性和原子性
图解CPU缓存模型
简化版CPU缓存模型请移步:java进阶(4)之volatile关键字深入详解
1. 高速缓存中的tag
表示数据所对应的内存地址,cacheline
表示数据本身,flag
表示数据状态(E表示exclusive
写,S表示shared
读)
2. Java
层面的读写数据,对应到硬件层面,就是主内存/高速缓存的读写;
3. 处理器与处理器之间的数据交互,或者说处理器与主内存的数据交互,是通过总线的嗅探机制来实现的,也可以说是缓存一致性协议(我把嗅探机制类比成java
层面的EventBus
);
4. 现代CPU
为了加快读写速度,加入了写缓冲器和无效队列,但是这样的话,数据只有读写完成了,才能进入到高速缓存/主内存,这样会导致数据无法及时更新,从而无法保证原子性,可见性。
内存屏障保障有序性和可见性
有序性保障
- 每个
volatile/synchronized
写操作前面,加StoreStore屏障,禁止上面的普通写和volatile/synchronized
重排; - 每个
volatile/synchronized
写操作后面,加StoreLoad屏障,禁止跟下面的volatile/synchronized
读/写重排; - 每个
volatile/synchronized
读操作前面,加LoadLoad屏障,禁止上面的普通读和volatile/synchronized
读重排; - 每个
volatile/synchronized
读操作后面,加LoadStore屏障,禁止下面的普通写和volatile/synchronized
读重排。
可见性保障
- 被
volatile/synchronized
关键字修饰的变量或者代码块在加载之前,会去硬件底层执行refresh
指令,将高速缓存/主内存的最新数据刷新到本地工作内存。 - 被
volatile/synchronized
关键字修饰的变量或者代码块在加载之后,会去硬件底层执行flush
执行,将数据刷到高速缓存/主内存中。
synchronized
中CAS机制保障原子性
被synchronized
关键字修饰的代码块或者方法,会被指令monitorenter
和monitorexit
包裹,包裹的模块可以称之为ObjectMonitor
,当有线程进入时,会执行CAS加锁,从而保证了原子性。
来源:CSDN
作者:罗小辉
链接:https://blog.csdn.net/a394268045/article/details/104604800