共计 979 个字符,预计需要花费 3 分钟才能阅读完成。
cpu 怎么与缓存的操作
cpu 会先从缓存中的缓存行读取内存值(不是内存地址),如果缓存没有命中,那么就从主内存中获取值,并且将获取到的值写入缓存行
volatile 修饰符
volatile 修饰过的变量转换成汇编语言会有个 lock 指令
lock 指令会引发下面的情况:
(1)、锁定当前的缓存行,将当前 cpu 缓存行的数据回写到主内存
(2)、这个回写主内存的操作会使得其他 cpu 缓存了该内存的地址的缓存行无效,迫使 cpu 下次来缓存读取时无法命中,进而去读取主内存数据,达到缓存一致性的协议
JMM 模型
JMM 线程通信的抽象模型
每个线程都有自己的本地内存(是 JMM 的抽象概念,不是真实存在),本地内存存储了对应线程的共享变量的副本(共享变量主体再主内存),如果线程 A、B 之间需要进行通信时,当线程 A 修改了它的本地内存的共享变量,就会将变量刷新回主内存,线程 B 就必须去主内存读线程 A 修改过后的值,并且存在自己的本地内存中
通过 volatile 来实现线程间的通信
线程 A 对有 volatile 修饰的变量 C 进行写操作时,JMM 会把对应本地内存的共享变量刷新回主内存,并且线程 B 想要读取变量 C,JMM 会将它得本地内存包含 C 的值置为无效,强制线程 B 去读主内存中 C 的值
指令重排序
分为 3 块:java 编译器重排序、指令级重排序(cpu 级别)、内存系统重排序(cpu 级别),可以通过 volatile 修饰符在读操作或者写操作的前后加入内存屏障
规则:
(1)、当第二个操作是 volatile 写时,不管第一个操作是什么,都不能重排序,而且会将第二个操作之前的普通写操作回写主内存,达到前面的普通写操作都对别的线程可见
(2)、当第一个 volatile 是读操作时,不管第二个操作是什么,都不能重排序
(3)、当第一个 volatile 操作是写,第二个 volatile 操作是读是,2 着都不能重排序
与 synchronized 修饰符的区别
既然 volatile 能够在 A 线程的写操作让别的线程的缓存行无效,如果别的线程要读缓存行就必须从主内存读,但是如果在高并发下的读写操作只用 volatile 修饰变量会导致线程 A 刚针对这个变量进行修改并且将别的线程的缓存行置为无效,但是线程 B 已经拿到这个变量的值了,但是这个变量的值是无效的,所以,volatile 是不能保证数据读写的安全性,一旦涉及到这个就必须通过 synchronized 修饰