Java 内存模型(JMM)是一种抽象的概念,并不真实存在,它描述了一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段、静态字段和构成数组对象的元素)的访问方式。JVM中的堆内存用来存储对象实例,堆内存是被所有线程共享的运行时内存区域,因此它存在可见性问题。Java内存模型定义了线程和主存间的抽象关系:线程之间的共享变量存储在主存中,每个线程有一个私有的本地内存,本地内存中存储了该线程共享变量的副本。需要注意的是本地内存是Java内存模型的一个抽象概念,并不真实存在,它涵盖了缓存、写缓冲区、寄存器等区域。Java内存模型的抽象示意图如下所示:

线程A与线程B要通信的话,要经历下面两个步骤:

  1. 线程A把线程A本地内存中更新过的内存共享变量刷新到主存;
  2. 线程B到主存中取线程A之前更新过的共享变量。

所以我们在执行下面语句:

1
int i = 0;

语句所在的线程会对变量i所在的缓存进行赋值操作,然后再写入主存中,而不是直接将数值0写入主存。

为了有个直观的感受,我们用一段代码来实现一下。先定义一个类VolatileDemo,包含一个running字段和一个stop方法:

1
2
3
4
5
6
7
public class VolatileDemo {
public boolean running = true;

public void stop() {
running = false;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void main(String[] args) {
VolatileDemo demo = new VolatileDemo();
System.out.println(Thread.currentThread().getName() + " start");
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " start");
while (demo.running) {

}
System.out.println(Thread.currentThread().getName() + " end");
}).start();

try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
demo.stop();
System.out.println(Thread.currentThread().getName() + " end");
}

我们在main方法,即主线程中创建VolatileDemo对象实例,然后新创建一个子线程,进入while循环不断的判断running属性。我们让主线程sleep1000毫秒,然后执行stop更新running的值为false,按正常逻辑如果running = false的话,子线程就会执行完,打印end,然后整个程序执行完毕。我们运行程序看一下:

发现子线程并没有打印end,并且程序一直在运行,没有结束退出。说明主线程更新的running = false,子线程并不知道。

示例代码已上传Github

关注我