1.什么是重排序
首先来看一个代码案例,尝试分析一下 x 和 y 的运行结果。
import java.util.concurrent.CountDownLatch;
/**
* 演示重排序的现象,直到达到某个条件才停止,测试小概率事件
*/
public class OutOfOrderExecution {
private static int x = 0, y = 0;
private static int a = 0, b = 0;
public static void main(String[] args) throws InterruptedException {
int i = 0;
while (true) {
i++;
x = 0;
y = 0;
a = 0;
b = 0;
CountDownLatch latch = new CountDownLatch(3);
Thread one = new Thread(new Runnable() {
@Override
public void run() {
try {
latch.countDown();
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
a = 1;
x = b;
}
});
Thread two = new Thread(new Runnable() {
@Override
public void run() {
try {
latch.countDown();
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
b = 1;
y = a;
}
});
two.start();
one.start();
latch.countDown();
one.join();
two.join();
String result = "第" + i + "次 (x = " + x + ", y = " + y + ")";
if (x == 0 && y == 0) {
System.out.println(result);
break;
} else {
System.out.println(result);
}
}
}
}
经过分析可知,线程 one 的第 33 行和第 34 行和线程 two 的第 47 行和第 48 行是核心代码,这 4 行代码的执行顺序决定了最终 x 和 y 的结果。
根据大多数人对多线程的认知,在线程 one 内部,第 33 行和第 34 行代码的执行顺序是不会改变的,即 a=1 会在 x=b 前执行;在线程 two 内部,第 47 行和第 48 行代码的执行顺序是不会改变的,即 b=1 会在 y=a 前执行。在此前提下,x 和 y 的最终结果一共有 3 种情况:
- 最终结果是 x=0, y=1,可能的执行顺序是 a=1; x=b; b=1; y=a;
- 最终结果是 x=1, y=0,可能的执行顺序是 b=1; y=a; a=1; x=b;
- 最终结果是 x=1, y=1,可能的执行顺序是 b=1; a=1; x=b; y=a;
然而,在实际运行过程中,会出现 x=0, y=0 的结果,这是因为重排序发生了,其中一种可能的代码执行顺序是 y=a; a=1; x=b; b=1;
至此,便可解答一个问题:什么是重排序?
在线程内部的两行代码的实际执行顺序和代码在Java文件中的顺序不一致,代码指令并不是严格按照代码语句顺序执行的,它们的顺序被改变了,这就是重排序。
2.重排序的好处
对比下面重排序前后的指令优化,我们可以发现,重排序的好处是可以提高处理速度。
3.重排序的三种情况
- 编译器优化:包括JVM,JIT编译器等。
- CPU指令重排:就算编译器不发生重排,CPU也可能对指令进行重排。
- 内存的“重排序”:线程 A 的修改线程 B 却看不到,引出可见性问题。
4.用volatile修正重排序问题
使用 volatile 关键字修正上面的 OutOfOrderExecution 类,加了 volatile 后,就不会出现 (x=0, y=0) 的情况了。
import java.util.concurrent.CountDownLatch;
/**
* 使用volatile关键字修正重排序问题
*/
public class OutOfOrderExecution {
private volatile static int x = 0, y = 0;
private volatile static int a = 0, b = 0;
public static void main(String[] args) throws InterruptedException {
int i = 0;
while (true) {
i++;
x = 0;
y = 0;
a = 0;
b = 0;
CountDownLatch latch = new CountDownLatch(3);
Thread one = new Thread(new Runnable() {
@Override
public void run() {
try {
latch.countDown();
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
a = 1;
x = b;
}
});
Thread two = new Thread(new Runnable() {
@Override
public void run() {
try {
latch.countDown();
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
b = 1;
y = a;
}
});
two.start();
one.start();
latch.countDown();
one.join();
two.join();
String result = "第" + i + "次 (x = " + x + ", y = " + y + ")";
if (x == 0 && y == 0) {
System.out.println(result);
break;
} else {
System.out.println(result);
}
}
}
}