一、Java内存模型(JMM)是什么?
Java内存模型(Java Memory Model, JMM)是Java多线程编程中共享内存的访问规则,定义了线程如何与主内存(Main Memory)和工作内存(Work Memory)交互,解决多线程并发中的原子性、可见性、有序性问题。确保多线程程序在不同架构的处理器和内存系统上都能正确运行。
二、核心概念
-
主内存与工作内存
-
主内存:所有线程共享的内存区域,存储变量(实例字段、静态字段等),类似于计算机中的物理内存。
-
工作内存:每个线程私有的内存区域,存储主内存变量的副本。
-
交互规则:线程只能操作工作内存中的变量副本,修改后需同步回主内存。
-
-
原子性、可见性、有序性
-
原子性:操作不可分割(如
AtomicInteger
),要不全部完成,要不全部不做。-
基本数据类型的赋值操作(如int a = 10)。
-
使用synchronized关键字或Lock接口实现的同步代码块。
public class Counter { private int count = 0; public synchronized void increment() { count++; } }
-
-
可见性:一个线程对变量的修改能被其他线程立刻看到(
volatile
、synchronized
)。-
volatile关键字:确保变量的修改立即刷新到主内存,其他线程读取时能获取最新值。
-
synchronized关键字:线程退出同步块时,会将工作内存中的变量写回主内存。
public class VolatileExample { private volatile boolean running = true; public void stop() { running = false; } public void run() { while (running) { // do something } } }
-
-
有序性:代码执行顺序与程序顺序一致(防止指令重排序)。
-
happens-before原则:定义了操作的先后顺序。
-
内存屏障:防止指令重排序。
-
-
-
Happens-Before原则 JMM定义的保证有序性的规则,若操作A happens-before 操作B,则A的结果对B可见。 常见规则:
-
程序顺序规则 :在单线程中,代码的执行顺序符合书写顺序。
-
volatile
变量规则 -
锁规则(
synchronized
或Lock
) -
线程启动/终止规则
-
传递性规则
-
三、关键机制
-
volatile关键字
-
保证变量的可见性:直接读写主内存,禁止缓存。
-
禁止指令重排序:通过插入内存屏障(Memory Barrier)。
-
示例:双检锁单例模式中的
volatile
修饰实例对象。
-
-
synchronized与锁
-
通过监视器锁(Monitor)实现原子性和可见性。
-
线程解锁前必须将变量同步回主内存。
-
-
final关键字
-
构造函数中对
final
域的写入,与后续引用赋值操作不会被重排序。 -
确保其他线程看到
final
变量时,其初始化已完成。
-
-
并发工具类
如CountDownLatch、CyclicBarrier等,用于更复杂的线程同步。
四、常见问题与面试考点
-
volatile能否保证原子性?
-
不能。
volatile
仅保证读写操作的原子性,但复合操作(如i++
)仍需同步。
-
-
DCL(双检锁)单例模式为什么要加volatile?
-
防止指令重排序导致其他线程获取未初始化完成的对象。
-
-
synchronized和ReentrantLock的区别?
-
synchronized
是JVM内置锁,自动释放;ReentrantLock
需手动加锁/解锁,支持公平锁、条件变量。
-
-
什么是内存可见性问题?如何解决?
-
线程A修改变量后未及时同步到主内存,线程B读取旧值。
-
解决:
volatile
、synchronized
、Lock
。
-
-
指令重排序的典型场景?
-
单例模式初始化:
new Object()
可能被拆分为分配内存、初始化对象、赋值引用三步,重排序后导致空指针。
-
-
什么是“线程安全的发布”?
-
对象在构造完成后才能被其他线程访问。
-
方式:
volatile
、静态初始化块、final
域、线程安全容器(如ConcurrentHashMap
)。
-
jJ五、代码示例:内存可见性问题
public class VisibilityDemo {
// 不加volatile可能导致死循环
private static volatile boolean flag = true;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
while (flag) {} // 线程可能读取到旧的flag值
System.out.println("Thread stopped.");
}).start();
Thread.sleep(1000);
flag = false; // 主线程修改flag
}
}
六、面试总结
-
必考知识点
-
volatile的作用与原理
-
synchronized的底层实现(Monitor、锁升级)
-
Happens-Before原则的规则
-
指令重排序与内存屏障
-
-
加分回答
-
结合JMM分析ConcurrentHashMap的线程安全设计。
-
对比JMM与物理内存模型(CPU缓存、MESI协议)。
-
掌握JMM是写出高并发代码的基础,也是大厂面试的必考领域!