在前几篇将Java内存模型的那些事基本上把这个域底层的概念都解释清楚了,聊聊高并发(三十五)Java内存模型那些事(三)理解内存屏障 这篇分析了在X86平台下,volatile,synchronized, CAS操作都是基于Lock前缀的汇编指令来实现的,关于Lock指令有两个要点:
1. lock会锁总线,总线是互斥的,所以lock后面的写操作会写入缓存和内存,可以理解为在lock后面的写缓存和写内存这两个动作称为了一个原子操作。当总线被锁时,其他的CPU是无法使用总线的,也就让其他的读写都等待lock的释放
2. Lock写完后,发起它的CPU的缓存和内存都是最新值,其他CPU相关的缓存行都会invalidate,后续的读/写就会发生缓存不命中,从内存重新加载最新值。
这里有个隐含的点,我没找到具体的资料,但是按照很多资料的说法: volatile的写操作相当于释放锁,volatile的读操作相当于进入锁可以做下面的推断:
volatile操作的是一个变量,而锁保护的程序段中涉及到的变量可以是多个,既然两者的效果是一样的,那么很可能lock后面的写会让高速缓存/写缓存区的所有脏数据都刷新回主存。只有这样volatile在可见性方面和锁保护的程序段的可见性才是行为一致的。
理解这个很重要,因为和这篇讲的Happens-before传递性有关系。Happens-before刚看到的时候从语言上看很难理解,觉得是废话,但是它实际描述的问题其实是可见性的问题,顺带着有一些由于防止重排序而带来的有序性的问题。聊聊高并发(三十三)Java内存模型那些事(一)从一致性(Consistency)的角度理解Java内存模型 这篇说了,内存模型是一致性这个问题域里面的,一致性问题只涉及到了可见性和有序性这两种特性,不包含原子性,所以Happens-before实际上是一系列的一致性的约束,所以它涉及到了可见性和有序性的意思,但没有原子性的含义。
happens-before俗解 这篇文章已经写的很清楚了,我这边再结合上一篇内存屏障的一些概念锦上添花一下,进一步说明这个问题
下面这些Happens-before的规则是从JSR 133 (Java Memory Model) FAQ 摘出来的,一条条看
其他还有一些Happens-before规则,比如CAS操作,原子变量的修改都有Happens-before的含义,另外Happens-before具备传递性,比如 A happens beofre B, B happens before C, 那么A肯定 happens before C。
为什么具备传递性呢,原因还是在开篇的时候说的,lock/内存屏障不仅仅把当前的地址的数据原子的写到缓存和内存,肯定也把这之前CPU缓存/write buffer的脏数据写回到主内存了,这样就实现了Happens before的传递性。
所以所有用到volatile ,synchronized, CAS的地方都具备Happens before的传递性,显式锁和原子变量底层都是基于CAS来实现的,当然用到它们的时候也具备了Happens before的传递性。
所以下面这个例子就很好理解了,比如 y是volatile变量或者是原子变量/同步器类等等用到CAS的
线程A 线程B
y = 2 b = x
如果在时间顺序上y=2这个对被同步的变量的写先发生于 a = y 这个对被同步的变量的读,那么可以肯定的说 b = x = 1。
有人问 x = 1会不会被重排到 y =2 之后,答案是不会,因为y是个被同步的变量,防止重排序, x 不会跨越内存屏障排到y=2之后,所以
b = x同样也不会被重排序到 a = y前面,因为 y是被同步的变量,内存屏障同样不会让屏障后面的操作跨越到前面去
所以只要 y =2 写操作发生在 a = y读操作之前,那么最后 x = 1 肯定先于 b=x,所以 b = 1
参考资料:
happens-before俗解