目录
[1.1 现代计算机内存架构与并发挑战](#1.1 现代计算机内存架构与并发挑战)
[1.2 JMM的内存抽象与核心概念](#1.2 JMM的内存抽象与核心概念)
[2.1 多层级重排序原理分析](#2.1 多层级重排序原理分析)
[2.2 内存屏障类型与语义](#2.2 内存屏障类型与语义)
[3.1 Happens-Before的8大核心规则](#3.1 Happens-Before的8大核心规则)
[3.2 Happens-Before的实战应用](#3.2 Happens-Before的实战应用)
[4.1 ConcurrentHashMap的JMM应用](#4.1 ConcurrentHashMap的JMM应用)
[4.2 CopyOnWriteArrayList的写时复制机制](#4.2 CopyOnWriteArrayList的写时复制机制)
[5.1 常见内存可见性问题排查](#5.1 常见内存可见性问题排查)
[5.2 JMM最佳实践总结](#5.2 JMM最佳实践总结)
摘要
Java内存模型(JMM)是理解Java并发编程的基石,它定义了多线程环境下内存可见性、原子性和有序性的核心规则。本文从硬件内存架构出发,深入解析JMM的内存抽象、重排序机制,重点剖析Happens-Before规则的8大原则及其实现原理。通过大量代码示例和内存屏障分析,帮助开发者从根本上避免内存可见性问题,写出线程安全的并发程序。
第一章:从硬件内存架构到Java内存模型
1.1 现代计算机内存架构与并发挑战

硬件层级的并发挑战:
-
缓存一致性:MESI协议保证最终一致性,但存在时间窗口
-
内存重排序:编译器、处理器多级优化导致指令重排
-
Store Buffer:写操作异步化引入可见性延迟
-
无效化队列:缓存失效通知的异步处理
java
// 可见性问题示例
public class VisibilityProblem {
private static boolean ready = false;
private static int number = 0;
public static void main(String[] args) {
new Thread(() -> {
while (!ready) {
// 可能永远循环,看不到ready的更新
Thread.yield();
}
System.out.println(number); // 可能输出0
}).start();
number = 42;
ready = true; // 可能重排序到number赋值前
}
}
1.2 JMM的内存抽象与核心概念
Java内存模型通过抽象层次屏蔽硬件差异,提供一致的内存语义。
java
/**
* JMM内存交互的8种原子操作
*/
class JMMMemoryOperations {
// 主内存操作
interface MainMemory {
void lock(); // 锁定
void unlock(); // 解锁
void read(); // 读取
void write(); // 写入
}
// 工作内存操作
interface WorkingMemory {
void load(); // 加载
void use(); // 使用
void assign(); // 赋值
void store(); // 存储
}
// 8种操作的内存交互规则
public class MemoryInteractionRules {
// 1. read/load必须顺序执行,但不保证连续
// 2. store/write必须顺序执行,但不保证连续
// 3. assign操作后必须伴随store/write
// 4. 新变量只能在主内存"诞生"
// 5. 变量同一时刻只被一个线程lock
// 6. unlock前必须执行store/write
// 7. 未lock不能unlock
// 8. unlock前必须将数据同步回主内存
}
}
JMM的三大特性:
-
原子性 (Atomicity) -
synchronized、原子类保证 -
可见性 (Visibility) -
volatile、synchronized保证 -
有序性 (Ordering) -
volatile、synchronized、Happens-Before规则
java
// JMM内存交互示例
public class JMMExample {
private int sharedVar = 0;
private volatile boolean flag = false;
public void writer() {
sharedVar = 1; // 普通写操作
flag = true; // volatile写,建立内存屏障
}
public void reader() {
if (flag) { // volatile读,刷新工作内存
System.out.println(sharedVar); // 保证看到1
}
}
}
第二章:重排序与内存屏障机制
2.1 多层级重排序原理分析

重排序的三种类型:
-
编译器重排序:在不改变单线程语义下的优化
-
处理器重排序:指令级并行优化(流水线、乱序执行)
-
内存系统重排序:缓存体系导致的内存操作乱序
java
// 重排序导致的问题示例
public class ReorderingProblem {
private static int x = 0, y = 0;
private static int a = 0, b = 0;
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 1000000; i++) {
x = y = a = b = 0;
Thread one = new Thread(() -> {
a = 1; // 操作1
x = b; // 操作2
});
Thread two = new Thread(() -> {
b = 1; // 操作3
y = a; // 操作4
});
one.start();
two.start();
one.join();
two.join();
// 可能出现x=0且y=0,因为操作1和2可能重排序
if (x == 0 && y == 0) {
System.out.println("重排序发生: " + i);
}
}
}
}
2.2 内存屏障类型与语义
内存屏障是阻止重排序的关键机制,不同处理器架构有不同的屏障指令。
java
/**
* 内存屏障类型详解
*/
public class MemoryBarrierTypes {
// LoadLoad屏障:Load1; LoadLoad; Load2
// 确保Load1先于Load2及后续加载操作
// StoreStore屏障:Store1; StoreStore; Store2
// 确保Store1写入对其他处理器可见先于Store2
// LoadStore屏障:Load1; LoadStore; Store2
// 确保Load1先于Store2及后续存储操作
// StoreLoad屏障:Store1; StoreLoad; Load2
// 确保Store1写入对其他处理器可见先于Load2
// 全能屏障,开销最大
public class BarrierExamples {
private volatile int guard = 0;
private int data = 0;
public void publish() {
data = 42;
// StoreStore屏障,确保data写入在guard之前可见
guard = 1; // volatile写包含StoreStore + StoreLoad
}
public void consume() {
if (guard == 1) { // volatile读包含LoadLoad + LoadStore
// LoadLoad屏障,确保guard读取在data之前
System.out.println(data); // 保证看到42
}
}
}
}
JVM中的内存屏障策略:
java
// volatile内存屏障插入策略
public class VolatileBarrierStrategy {
// volatile写操作前后的屏障
public class VolatileWrite {
public void write() {
// 写操作前
// StoreStore屏障(防止上面普通写与volatile写重排序)
volatileVar = newValue;
// 写操作后
// StoreLoad屏障(防止volatile写与后面操作重排序)
}
}
// volatile读操作前后的屏障
public class VolatileRead {
public void read() {
// 读操作前
// LoadLoad屏障(防止上面普通读与volatile读重排序)
// LoadStore屏障(防止上面普通读与后面普通写重排序)
int value = volatileVar;
// 读操作后
// 无屏障(LoadLoad已在前面)
}
}
}
第三章:Happens-Before规则深度解析
3.1 Happens-Before的8大核心规则
Happens-Before是JMM的核心概念,定义了两个操作之间的偏序关系。

8大Happens-Before规则详解:
java
/**
* Happens-Before规则代码演示
*/
public class HappensBeforeRules {
// 1. 程序顺序规则:线程内书写顺序
public void programOrderRule() {
int x = 1; // 操作A
int y = 2; // 操作B
// A happens-before B
}
// 2. 监视器锁规则
private final Object lock = new Object();
private int sharedData = 0;
public void monitorLockRule() {
synchronized(lock) {
sharedData = 42; // 解锁happens-before后续加锁
}
// 后续 synchronized(lock) 能看到sharedData=42
}
// 3. volatile变量规则
private volatile boolean flag = false;
private int data = 0;
public void volatileRule() {
data = 42; // 普通写
flag = true; // volatile写
// data=42 happens-before flag=true(程序顺序)
if (flag) { // volatile读
System.out.println(data); // 保证看到42
}
}
// 4. 线程启动规则
public void threadStartRule() {
final int[] result = new int[1];
Thread t = new Thread(() -> {
result[0] = 42; // 线程体
});
t.start(); // start() happens-before 线程内所有操作
t.join();
System.out.println(result[0]); // 保证看到42
}
// 5. 线程终止规则
public void threadTerminationRule() throws InterruptedException {
final int[] result = new int[1];
Thread t = new Thread(() -> {
result[0] = 42;
});
t.start();
t.join(); // 线程内操作 happens-before join()返回
System.out.println(result[0]); // 保证看到42
}
// 6. 中断规则
public void interruptRule() {
Thread t = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
// 循环
}
// interrupt() happens-before 检测到中断
});
t.start();
t.interrupt(); // 中断操作 happens-before 线程检测到中断
}
// 7. 传递性规则
public void transitivityRule() {
// 如果A happens-before B, B happens-before C
// 那么A happens-before C
}
// 8. 对象终结规则
public void finalizerRule() {
Object obj = new Object() {
@Override
protected void finalize() {
// 构造方法 happens-before finalize()
}
};
}
}
3.2 Happens-Before的实战应用
理解Happens-Before规则的关键在于识别"可见性保证链"。
java
/**
* 实战:基于Happens-Before的安全发布模式
*/
public class SafePublicationPatterns {
// 模式1:静态初始化(最安全)
public static class StaticInitializer {
private static final Resource resource = new Resource();
public static Resource getInstance() {
return resource; // 利用类加载的happens-before
}
}
// 模式2:volatile发布
public static class VolatilePublication {
private volatile Resource resource;
public void initialize() {
resource = new Resource(); // 利用volatile的happens-before
}
public Resource getResource() {
return resource; // 保证看到完全构造的对象
}
}
// 模式3:final字段
public static class FinalFieldPublication {
private final Resource resource;
public FinalFieldPublication() {
this.resource = new Resource(); // 构造函数的happens-before
}
public Resource getResource() {
return resource; // final字段的特殊内存语义
}
}
// 错误的发布模式
public static class UnsafePublication {
private Resource resource;
public void initialize() {
resource = new Resource(); // 可能重排序,其他线程看到未完全构造的对象
}
public Resource getResource() {
return resource; // 可能看到null或部分构造的对象
}
}
}
/**
* 双重检查锁定(DCL)的正确实现
*/
public class DoubleCheckedLocking {
private volatile static Resource resource;
public static Resource getInstance() {
Resource result = resource; // 第一次检查(无锁)
if (result == null) {
synchronized (DoubleCheckedLocking.class) {
result = resource;
if (result == null) {
result = new Resource();
resource = result; // volatile写建立happens-before
}
}
}
return result;
}
static class Resource {
private final int data;
public Resource() {
this.data = 42; // 构造函数在volatile写之前完成
}
}
}
第四章:JMM在并发容器中的实现
4.1 ConcurrentHashMap的JMM应用
java
/**
* ConcurrentHashMap中的内存语义分析
*/
public class ConcurrentHashMapJMM {
// Node节点的volatile语义
static class Node<K,V> {
final int hash;
final K key;
volatile V val; // volatile保证可见性
volatile Node<K,V> next; // volatile保证链表操作可见性
Node(int hash, K key, V val, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.val = val;
this.next = next;
}
}
// 表格初始化的内存语义
public class TableInitialization {
private transient volatile Node<K,V>[] table;
@SuppressWarnings("unchecked")
private final Node<K,V>[] initTable() {
Node<K,V>[] tab;
while ((tab = table) == null) {
synchronized (this) {
if ((tab = table) == null) {
// 初始化操作
tab = new Node[16];
// StoreStore屏障,确保数组完全初始化后再赋值
table = tab; // volatile写建立happens-before
}
}
}
return tab;
}
}
// get操作的无锁读
public V get(Object key) {
Node<K,V>[] tab;
Node<K,V> e;
int h = spread(key.hashCode());
// 读取volatile的table引用
if ((tab = table) != null && tab.length > 0 &&
(e = tabAt(tab, (tab.length - 1) & h)) != null) {
// 读取volatile的Node值
if (e.hash == h) {
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
return e.val; // volatile读,保证最新值
}
}
return null;
}
}
4.2 CopyOnWriteArrayList的写时复制机制
java
/**
* CopyOnWriteArrayList的JMM实现
*/
public class CopyOnWriteArrayListJMM<E> {
// volatile保证数组引用的可见性
private transient volatile Object[] array;
final Object[] getArray() {
return array; // volatile读,看到最新的数组引用
}
final void setArray(Object[] a) {
array = a; // volatile写,发布新数组
}
public boolean add(E e) {
synchronized (lock) {
Object[] elements = getArray();
int len = elements.length;
// 复制新数组
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
// volatile写建立happens-before
setArray(newElements); // 新数组对其他线程立即可见
return true;
}
}
public E get(int index) {
return get(getArray(), index); // 无锁读,volatile读保证可见性
}
@SuppressWarnings("unchecked")
private E get(Object[] a, int index) {
return (E) a[index];
}
}
第五章:内存模型实战与故障排查
5.1 常见内存可见性问题排查
java
/**
* 内存可见性问题的诊断模式
*/
public class MemoryVisibilityDiagnosis {
// 模式1:失效数据问题
public class StaleDataProblem {
private boolean running = true; // 非volatile,可能看到失效值
public void stop() {
running = false; // 写操作可能不被其他线程立即可见
}
public void run() {
while (running) { // 可能永远看不到false
// 工作
}
}
}
// 修复:使用volatile
public class StaleDataFix {
private volatile boolean running = true;
public void stop() {
running = false; // volatile写,立即可见
}
public void run() {
while (running) { // volatile读,看到最新值
// 工作
}
}
}
// 模式2:非原子64位操作
public class NonAtomic64Bit {
private long value = 0L; // long不是原子操作
public void setValue(long v) {
this.value = v; // 可能看到高32位和低32位来自不同写入
}
public long getValue() {
return value; // 可能读到中间状态
}
}
// 修复:使用volatile或原子类
public class Atomic64BitFix {
private volatile long value = 0L; // volatile保证long/double原子性
public void setValue(long v) {
this.value = v;
}
public long getValue() {
return value;
}
}
}
/**
* 内存可见性测试工具
*/
public class VisibilityTestTool {
// 测试工具:检测重排序
public static void testReordering() throws InterruptedException {
for (int i = 0; i < 100000; i++) {
final int[] results = new int[2];
final boolean[] flags = new boolean[2];
Thread t1 = new Thread(() -> {
results[0] = 1;
flags[0] = true; // 可能重排序
});
Thread t2 = new Thread(() -> {
results[1] = 1;
flags[1] = true; // 可能重排序
});
t1.start(); t2.start();
t1.join(); t2.join();
// 检测是否看到重排序效果
if (flags[0] && flags[1] && (results[0] == 0 || results[1] == 0)) {
System.out.println("检测到重排序: " + i);
}
}
}
// 内存屏障测试
public class MemoryBarrierTest {
private int x = 0, y = 0;
private volatile boolean barrier = false;
public void testBarrier() throws InterruptedException {
Thread writer = new Thread(() -> {
x = 1;
y = 1;
barrier = true; // 内存屏障
});
Thread reader = new Thread(() -> {
while (!barrier) {
// 等待屏障
}
// 屏障后保证看到x=1, y=1
System.out.println("x=" + x + ", y=" + y);
});
writer.start();
reader.start();
writer.join();
reader.join();
}
}
}
5.2 JMM最佳实践总结
java
/**
* JMM编程最佳实践
*/
public class JMMBestPractices {
// 实践1:正确使用volatile
public class VolatileBestPractice {
// 适合状态标志位
private volatile boolean shutdownRequested;
public void shutdown() {
shutdownRequested = true;
}
public void doWork() {
while (!shutdownRequested) {
// 执行工作
}
}
// 不适合复合操作
private volatile int count = 0;
public void unsafeIncrement() {
count++; // 非原子操作,volatile不保证原子性
}
}
// 实践2:安全发布模式
public class SafePublication {
// 方式1:静态初始化
public static final Resource resource1 = new Resource();
// 方式2:volatile引用
private volatile Resource resource2;
public void initialize() {
resource2 = new Resource();
}
// 方式3:final字段
private final Resource resource3;
public SafePublication() {
this.resource3 = new Resource();
}
}
// 实践3:避免逸出
public class ThisEscape {
private final int number;
public ThisEscape(EventSource source) {
source.registerListener(new EventListener() {
public void onEvent(Event e) {
// 可能看到未初始化的number(0)
doSomething(e, number);
}
});
this.number = 42; // 构造函数未完成就发布this引用
}
// 修复:使用工厂方法
public static ThisEscape newInstance(EventSource source) {
ThisEscape instance = new ThisEscape();
source.registerListener(instance.new Listener());
return instance;
}
private ThisEscape() {
this.number = 42; // 完全初始化后再发布
}
}
}
/**
* 性能优化建议
*/
public class JMMPerformanceTips {
// 提示1:减少共享变量
public class ReduceSharing {
// 坏:多个线程频繁修改
private volatile int counter;
// 好:线程局部变量
private static final ThreadLocal<Integer> localCounter =
ThreadLocal.withInitial(() -> 0);
public void increment() {
int local = localCounter.get();
localCounter.set(local + 1);
// 定期同步到共享变量
if (local % 100 == 0) {
synchronized (this) {
counter += local;
localCounter.set(0);
}
}
}
}
// 提示2:使用不可变对象
public final class ImmutableValue {
private final int value;
private final String name;
public ImmutableValue(int value, String name) {
this.value = value;
this.name = name;
}
// 无需同步,线程安全
public int getValue() { return value; }
public String getName() { return name; }
}
}
总结
Java内存模型是并发编程的理论基础,理解JMM和Happens-Before规则对于编写正确的并发程序至关重要。通过本文的深入分析,我们可以看到:
-
JMM抽象:在硬件内存模型之上提供一致性的内存语义
-
重排序控制:通过内存屏障限制编译器和处理器的优化
-
Happens-Before:建立操作间的偏序关系,保证可见性
-
实战应用:在并发容器和工具类中广泛运用JMM原则
掌握这些原理,能够帮助开发者从根本上理解并发bug的成因,写出更安全、高效的多线程代码。