volatile内存屏障的一些问题
前言
Java 编译器会在生成指令系列时在适当的位置会插入内存屏障指令来禁止特定类型的处理器重排序。下面是JMM针对编译器指定的volatile重排序规则表:
从规则表中可以看出:
当第一个操作是volatile读的时候,不管第二个操作是什么操作都禁止重排序。
当第一个操作是volatile写的时候,第二个操作是volatile 读或写时,都不能重排序。
当第二个操作是volatile写时,第一个操作都不能重排序。
1.代码
代码如下(示例):
public class ReorderThread {
// 全局共享的变量
private static int a = 0 ;
private static int b = 0;
private volatile static int x = 0 ;
private volatile static int y = 0;
public static void main(String[] args) throws InterruptedException {
int i = 0;
while (true) {
i++;
a = 0;
b = 0;
x = 0;
y = 0;
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
a = 1;
x = b;
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
b = 1;
y = a;
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("第" + i + "次(" + x + "," + y + ")");
if (x == 0 && y == 0) {
break;
}
}
}
}
2.实际结果
代码如下(示例):
第166455次(0,1)
第166456次(0,1)
第166457次(0,1)
第166458次(0,1)
第166459次(0,0)
总结
x,y,是volatile写,按规则表,不应该出现重排序,因而不会发生(0,0)的结果。
但实际上出现了这个结果,说明发生了重排序。
不知道为什么……
public class UnsafeFactory {
private UnsafeFactory(){}
private static Unsafe unsafe = null;
public static synchronized Unsafe getUnsafe() throws Exception{
if(unsafe != null){
return unsafe;
}
// 通过反射得到theUnsafe对应的Field对象
Field field = Unsafe.class.getDeclaredField("theUnsafe");
// 设置该Field为可访问
field.setAccessible(true);
// 通过Field得到该Field对应的具体对象,传入null是因为该Field为static的
unsafe = (Unsafe) field.get(null);
return unsafe;
}
}
public class Test2 {
// 全局共享的变量
private static int a = 0 ;
private volatile static int b = 0;
private static int x = 0 ;
private static int y = 0;
public static void main(String[] args) throws InterruptedException {
int i = 0;
while (true) {
i++;
a = 0;
b = 0;
x = 0;
y = 0;
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
a = 1;
x = b;
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
b = 1;
try {
UnsafeFactory.getUnsafe().storeFence();
} catch (Exception e) {
e.printStackTrace();
}
y = a;
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("第" + i + "次(" + x + "," + y + ")");
if (x == 0 && y == 0) {
break;
}
}
}
}
第452266次(0,1)
第452267次(0,0)
此示例证明volatile读之前未加内存屏障。
private volatile static int x = 0 ;
第303760次(0,1)
第303761次(0,0)
此示例证明volatile写之前未加内存屏障。
以上两个案例证明:1.第一个操作是普通读写,第二个操作无论是volatile读或者写,都有可能发生重排序。
private volatile static int a = 0 ;
第3213997次(0,1)
第3213998次(0,1)
不想往下运行了
此示例证明volatile写之后加了内存屏障。
private volatile static int b = 0;
x = b;
a = 1;
y = a;
try {
UnsafeFactory.getUnsafe().storeFence();
} catch (Exception e) {
e.printStackTrace();
}
b = 1;
if (x == 1 && y == 1) {
break;
}
第2269081次(0,1)
第2269082次(0,1)
此示例证明volatile读之后加了内存屏障。
总总结: 规则表只是理论,至少在x86上并未落地。规则表是不能作为使用依据,根据实验volatile规则的猜想是:
volatile读和volatile写之后会加内存屏障,后面的代码不会与他发生重排序,其他情况都有可能发生重排序。
简单记忆法:
1 x=a
2 y=b
在x或者a 任意一个添加volatile,则1,2不会发生重排序。
ps: 由于运行数量不是很多,本人机器性能一般,所以需要更多验证。