Wetts's blog

Stay Hungry, Stay Foolish.

0%

Java-关键字-violate的有序性.md

转自:https://blog.csdn.net/qq_21729419/article/details/113733500

中心思想

使普通全局变量的写对其他线程立即可见(使用volatile有序性来传递)

内存屏障

先来一堆有必要的废话

  • LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
  • StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
  • LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
  • StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。

Java内存模型允许编译器和处理器对指令重排序以提高运行性能,并且只会对不存在数据依赖性的指令重排序。

为了实现volatile的有序性内存语义(jdk5之后),编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。

  • 在每个volatile写操作的前面插入一个StoreStore屏障。
  • 在每个volatile写操作的后面插入一个StoreLoad屏障。
  • 在每个volatile读操作的前面插入一个LoadLoad屏障。
  • 在每个volatile读操作的后面插入一个LoadStore屏障

volatile写操作插入内存屏障后生成的指令序列如下图所示。
1

volatile读操作插入内存屏障后生成的指令序列如下图所示。
2

传递性 :如果操作A先行发生于操作B,操作B先行发生于操作C,那就可以得出操作A先行发生于操作C的结论。

相信大家对上面的内容很熟悉了,但是代表啥意思呢,估计你还是一头雾水。我们举个例子

1
2
3
4
5
6
7
8
9
10
11
12
int e = 0;
volatile f = 0;


//thread 1
e = 1; //操作 A
f = 1; //操作 B

//thread 2

int j = f; //操作 C
int k = e; //操作 D

我们假设 thread 1 执行结束之后thread 2执行,由于f是volatile变量,那么可知B操作的写,对于C操作的读是可见的。那么可得B先发生与C,每个线程单独来看, A先发生与B, C先发生与D,最后我们再加上传递性可知。

A->B->C->D

至此我们可以得到普通变量e的的写入对于其他线程立即可见(注意变量f在其中起到的作用)

Reentrantlock 也借助了volatile的这个特性

总结一下有序性的含义

  1. 禁止指令重排
  2. volatile写会将线程工作缓存(cpu缓存)中的所有数据写入主存
  3. volatile读会将线程工作缓存(cpu缓存)中的所有数据失效,读的时候需要从主存中取。

测试代码

前面举的例子比较难测试出反例(对普通变量的写,不通过volatile的有序性保证,其他线程不是立即可见),所以写了下面的测试代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static int a = 0;
static volatile int b = 0; //去掉volatile修饰会发生死循环,即变量a对于其他线程不是立即可见


public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while(a==0) {
int c = b;
}
});
thread.start();

Thread.sleep(200);

new Thread(() -> {
a = 1;
b = 1;
}).start();

thread.join();
}