Java 内存模型(一)
Java 内存模型(一)
Java 内存模型的抽象
在 java 中,所有实例域、静态域和数组元素存储在堆内存中,堆内存在线程之间共享。局部变量,方法定义参数和异常处理器参数不会在线程之间共享,它们不会有内存可见性问题,也不受内存模型的影响。
Java 线程之间的通信由 Java 内存模型控制,JMM 决定一个线程对共享变量的写入何时对另一个线程可见。从抽象的角度来看,JMM 定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读 / 写共享变量的副本。本地内存是 JMM 的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。Java 内存模型的抽象示意图如下:
重排序
利用下面代码观察重排序现象(可能看不到重排序现象,仅作介绍):
import java.util.concurrent.CountDownLatch;
public class ReorderTest {
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;
for (;;) {
i ++;
x = 0;
y = 0;
a = 0;
b = 0;
CountDownLatch latch = new CountDownLatch(1);
Thread one = new Thread(() -> {
try {
latch.await();
} catch (InterruptedException e) {
}
a = 1;
x = b;
});
Thread other = new Thread(() -> {
try {
latch.await();
} catch (InterruptedException e) {
}
b = 1;
y = a;
});
one.start();other.start();
latch.countDown();
one.join();other.join();
String result = "第" + i + "次 (" + x + "," + y + ")";
if(x == 0 && y == 0) {
System.err.println(result);
break;
} else {
System.out.println();
}
}
}
}
几秒后就可以得到 x == 0 && y == 0 这个结果,仔细看看代码就会知道,如果不发生重排序的话,这个结果是不可能出现的。
重排序由以下几种机制引起:
-
编译器优化:对于没有数据依赖关系的操作,编译器在编译的过程中会进行一定程度的重排。编译器是可以将 a = 1 和 x = b 换一下顺序的,因为它们之间没有数据依赖关系,同理,线程 2 也一样,那就不难得到 x == y == 0 这种结果了。
-
指令重排序:CPU 优化行为,也是会对不存在数据依赖关系的指令进行一定程度的重排,比如 CPU 流水线上指令乱序执行。
-
内存系统重排序:内存系统没有重排序,但是由于有缓存的存在,使得程序整体上会表现出乱序的行为。正是因为三级缓存结构的原因,所以才有了这个问题。