在多线程编程中,内存可见性、指令重排序和线程同步是开发者必须理解的核心概念。Java 内存模型(JMM,Java Memory Model)定义了一组规则,确保 Java 程序在并发环境下的线程安全性和一致性。本文将深入剖析 JMM 的原理,并通过代码示例展示如何正确控制并发。
Java 内存模型(JMM)是 Java 语言规范中的一部分,主要用于屏蔽不同 CPU 和操作系统的内存访问差异,提供一个一致的内存访问规则,以保证 Java 线程的正确执行。
JMM 主要涉及主内存、工作内存、可见性、原子性、有序性等几个关键点。
Java 线程执行时,每个线程都有自己的工作内存(CPU 缓存),而数据的真实存储在主内存(RAM) 中。
当一个线程修改了变量,但其他线程看不到修改结果时,就会发生可见性问题。
示例:可见性问题
class VisibilityTest {
private static boolean flag = true;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
while (flag) {
// 如果没有 volatile,CPU 可能会优化,导致 flag 不可见
}
System.out.println("线程退出");
}).start();
Thread.sleep(1000);
flag = false; // 主线程修改 flag
System.out.println("主线程修改 flag 为 false");
}
}
❌ 问题:
由于 flag
变量没有 volatile
修饰,子线程可能永远看不到主线程的修改,导致死循环。
✅ 解决方案:使用 volatile
private static volatile boolean flag = true;
volatile
关键字禁止 CPU 缓存优化,确保变量变更对所有线程可见。
原子性(Atomicity) 指一个操作不会被 CPU 中断,确保操作完整性。
例如,i++
在 Java 并发环境下不是原子操作,而是读 -> 加 1 -> 写 的三步操作,在多线程环境下可能产生竞态条件。
示例:非原子操作导致数据错误
class AtomicityTest {
private static int count = 0;
public static void main(String[] args) throws InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1000; i++) {
executor.submit(() -> count++);
}
executor.shutdown();
Thread.sleep(2000);
System.out.println("最终 count 值:" + count);
}
}
❌ 可能出现的问题:
count++
不是原子操作,多线程并发执行时,最终 count
可能小于 1000。✅ 解决方案:使用 AtomicInteger
private static AtomicInteger count = new AtomicInteger(0);
executor.submit(() -> count.incrementAndGet());
JVM 可能会重排序代码执行顺序,提高 CPU 执行效率,但这可能导致意外的执行结果。
示例:DCL(双重检查锁)+ 指令重排序
class Singleton {
private static Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton(); // 可能发生重排序
}
}
}
return instance;
}
}
❌ 问题:
instance = new Singleton();
可能被 JVM 重排序,导致对象未完全初始化,其他线程访问时可能抛出 NullPointerException
。✅ 解决方案:使用 volatile
private static volatile Singleton instance;
volatile
关键字禁止指令重排序,确保对象初始化的顺序正确。
并发控制方式 | 作用 |
---|---|
volatile |
保证变量的 可见性,但不保证 原子性 |
synchronized |
既保证 可见性,又保证 原子性 |
Lock |
提供 更高级的锁机制,支持公平锁、可重入等 |
Atomic 类 |
确保操作 原子性,如 AtomicInteger |
ThreadLocal |
线程隔离,避免数据竞争 |
推荐使用 volatile
+ synchronized
class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
✅ 使用 volatile
防止指令重排序
✅ 使用 synchronized
确保并发安全
Java 内存模型(JMM)规定了多线程访问共享变量的规则,以解决 可见性、原子性和有序性 问题。
线程之间的数据不共享主内存,而是缓存到 CPU 缓存,volatile
可以解决可见性问题。
synchronized
和 Lock
机制可以确保线程安全,避免竞态条件。
Atomic
包提供了高效的无锁并发解决方案,如 AtomicInteger
。
掌握 JMM 的原理,才能写出高效、稳定的并发程序!