Java 指令重排序
Java 内存模型运行编译器和处理器对指令重排序以提高运行性能,并且只会对不存在数据依赖性的指令重排序。在单线程下可以保证最终的结果与程序顺序执行的结果一致,但是在多线程下就会存在问题。
例如:
int a = 1;(1)
int b = 2;(2)
int c = a + b;(3)
在上面代码中,变量 c 的值依赖 a 和 b 的值,所以重排序后能够保证 (3) 的操作在 (2)和(1)之后,但是(1)(2)谁先执行就不一定了,这在单线程下不会存在问题,因为并不影响最终的结果。
Public class Test {
private static int num = 0;
private static boolean ready = false;
public static class ReadThread extends Thread {
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
if (ready) {//(1)
System.out.println(num + num);//(2)
}
System.out.println("read thread ...");
}
}
}
public static class WriteRead extends Thread {
@Override
public void run() {
num = 2;//(3)
ready = true;//(4)
System.out.println("WriteRead set over");
}
}
public static void main(String[] args) throws InterruptedException {
ReadThread rt = new ReadThread();
rt.start();
WriteRead wr = new WriteRead();
wr.start();
Thread.sleep(10);
rt.interrupt();
System.out.println("main exit");
}
}
这段代码里面的变量没有被声明为 volatile的,也没有使用任何同步措施,所以在多线程下存在共享变量内存可见性问题。这里先不谈可见性问题,因为通过把变量声明为 volatile 的本身就可以避免指令重排序问题。
上面代码在不考虑内存可见性问题的情况下不一定会输出4。由于代码(1)(2)(3)(4)之间不存在依赖关系,所以写线程的代码(3)(4)可能被重排序为先执行(4)再执行(3),那么执行(4)后,读线程可能已经执行了(1)操作,并且在(3)执行前开始执行(2)操作,这时候输出结果为0而不是4。
重排序在多线程下会导致非预期的程序执行结果,而使用 volatile 修饰 ready 就可以避免重排序和内存可见性问题。
写 volatile 变量时,可以确保 volatile 写之前的操作不会被编译器重排序到 volatile 写之后。读 vola 变量时,可以确保 volatile 读之后的操作不会被编译器重排序到 volatile 读之前。