一、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是写出高并发代码的基础,也是大厂面试的必考领域!