面试官:什么是Java内存模型?("深入解析Java内存模型:面试官常问知识点详解")
原创
一、Java内存模型的概述
Java内存模型(Java Memory Model,JMM)是Java虚拟机(JVM)的一个核心概念,它定义了Java程序中各种变量(线程共享的变量)的访问规则,同时也涉及到JVM的运行时数据区。JMM决定了一个变量的写入何时对另一个线程可见,它是懂得并发编程和线程稳固的基础。
二、Java内存模型的组成
Java内存模型核心由以下几个部分组成:
- 主内存(Main Memory)
- 工作内存(Working Memory)
- 内存操作(Read、Use、Assign、Write)
- 内存屏障(Memory Barrier)
- 原子性(Atomicity)
- 可见性(Visibility)
- 有序性(Ordering)
三、主内存与工作内存
主内存是所有线程共享的内存区域,它存储了Java程序中的实例字段、静态字段和构成数组的元素。工作内存是每个线程私有的内存缓冲区,用于存储线程使用的变量的副本。
当线程对变量进行操作时,它会先从主内存中读取变量值到工作内存,然后在工作内存中对变量进行操作,操作完成后将新值写回主内存。
四、内存操作
Java内存模型定义了以下几种内存操作:
- Read:从主内存读取数据到工作内存。
- Use:在工作内存中使用变量。
- Assign:将工作内存中的数据赋值给变量。
- Write:将工作内存中的数据写回主内存。
五、内存屏障
内存屏障是JMM用来保证特定内存操作的执行顺序,它是一种同步机制。JMM通过插入特定类型的内存屏障来禁止特定类型的处理器重排序,确保内存操作的顺序性。
六、原子性
原子性指的是一个操作在其他线程看来是不可分割的,即线程在执行这些操作时不会被中断。JMM通过happens-before原则来保证原子性。以下是一些具有原子性的操作:
- 基本读取和写入操作(如对基本数据类型的读取和写入)。
- 使用volatile变量的读/写操作。
- 锁的获取和释放。
七、可见性
可见性指的是当一个线程修改了共享变量的值后,其他线程能够立即得知这个修改。JMM通过volatile和synchronized两个关键字来保证可见性。
使用volatile关键字修饰的变量,当一个线程写入该变量时,它会立即被更新到主内存中,同时其他线程读取该变量时,会从主内存中读取最新值。
使用synchronized关键字同步代码块或方法时,当一个线程进入同步代码块或方法前,它会清空工作内存中的共享变量值,当退出同步代码块或方法时,它会将工作内存中的共享变量的最新值刷新回主内存。
八、有序性
有序性指的是程序执行的顺序按照代码的先后顺序执行。JMM通过happens-before原则来保证有序性。以下是一些happens-before规则:
- 单线程内代码的执行顺序。
- 锁的获取一定出现在释放之前。
- volatile变量的写操作happens-before后续的读操作。
- 线程A启动线程B,线程B的线程启动happens-before线程B的线程终止。
- 线程A中的解锁操作happens-before线程B中的加锁操作(线程B尝试获取线程A拥有的锁)。
九、案例分析
以下是一个明了的Java程序,分析其内存操作过程:
public class VisibilityTest {
private static boolean flag = false;
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = true;
System.out.println("t1 set flag to true");
});
Thread t2 = new Thread(() -> {
while (!flag) {
// 循环等待flag变为true
}
System.out.println("t2 see flag is true");
});
t1.start();
t2.start();
}
}
在这个例子中,线程t1修改了共享变量flag的值,而线程t2需要读取这个值。由于flag没有使用volatile关键字修饰,使线程t1对flag的修改也许不会立即对线程t2可见。为了确保可见性,可以将flag声明为volatile:
private static volatile boolean flag = false;
十、总结
Java内存模型是懂得并发编程和线程稳固的基础。通过掌握JMM的概念和原理,我们可以更好地编写并发程序,避免内存可见性和有序性问题。在实际开发中,我们应该遵循JMM的规则,合理使用volatile和synchronized关键字,确保程序的正确性和性能。