在 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 流水线上指令乱序执行。
内存系统重排序:内存系统没有重排序,但是由于有缓存的存在,使得程序整体上会表现出乱序的行为。正是因为三级缓存结构的原因,所以才有了这个问题。