JMM 内存模型深度解析:从 CPU 缓存到 Happens-Before

0 阅读18分钟

前言:为什么要学习JMM

在Java并发编程的世界里,JMM(Java Memory Model,Java内存模型) 是一座绕不开的高山。无论你是:

  • 初学者:经常被线程安全问题搞得焦头烂额
  • 中级开发者:知道用synchronizedvolatile,却不知道为什么这样用
  • 高级开发者:需要在高并发场景下进行极致性能优化

理解JMM都是你成为Java并发编程高手的必经之路

学习JMM能帮助你:

  1. 理解并发bug的根本原因,而不是靠运气调试
  2. 正确使用volatilesynchronizedfinal等关键字
  3. 读懂JDK并发包源码(如ConcurrentHashMapAQS等)
  4. 在高并发场景下写出正确且高效的代码
  5. 在技术面试中展现扎实的基础功底

第一篇:基础篇 —— 理解JMM的本质

1.1 从硬件层面理解并发问题的根源

要理解JMM,首先必须理解为什么会存在并发问题。这一切都要从现代计算机的硬件架构说起。

1.1.1 CPU缓存架构

现代CPU为了解决内存访问速度远低于CPU运算速度的问题,引入了多级缓存架构: JMM-CPU多级缓存架构图

问题来了:当多个CPU核心同时操作同一份数据时,由于每个核心都有自己的缓存,就会出现缓存一致性问题

1.1.2 缓存一致性问题示例

// 假设初始值: x = 0
// CPU Core 0                    CPU Core 1
   load x → 缓存(x=0)            load x → 缓存(x=0)
   x = x + 1                     x = x + 1
   缓存(x=1)                     缓存(x=1)
   store x → 主内存(x=1)         store x → 主内存(x=1)
   
// 期望结果: x = 2
// 实际结果: x = 1  ← 出现数据不一致!

1.1.3 处理器优化与指令重排序

除了缓存一致性问题,现代CPU还会进行指令重排序以提高执行效率:

// 源代码顺序
int a = 1;      // 语句1
int b = 2;      // 语句2
int c = a + b;  // 语句3

// 可能的执行顺序(语句1和语句2可能重排)
int b = 2;      // 语句2
int a = 1;      // 语句1
int c = a + b;  // 语句3

重排序的三个层面:

层面描述示例
编译器优化重排序编译器在不改变单线程语义的前提下重新安排语句执行顺序JIT编译优化
指令级并行重排序现代CPU采用ILP技术并行执行多条指令流水线技术
内存系统重排序由于缓存和读写缓冲区的存在,加载和存储操作可能乱序Store Buffer

1.2 JMM是什么

1.2.1 官方定义

JMM(Java Memory Model) 是Java虚拟机规范中定义的一套规则,用于屏蔽各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果。 JMM抽象架构图

1.2.2 JMM的抽象结构

JMM将内存分为两个部分:

jmm-main-working-memory.drawio.png 关键点:

  • 主内存(Main Memory):所有线程共享的内存区域
  • 工作内存(Working Memory):每个线程私有的内存区域,是主内存中变量的副本
  • 线程对变量的所有操作都必须在工作内存中进行,不能直接操作主内存
  • 不同线程之间无法直接访问对方的工作内存,线程间通信必须通过主内存来完成

1.2.3 JMM与JVM内存区域的关系

注意:JMM与JVM运行时数据区域(堆、栈、方法区等)是不同层面的概念!

概念关注点描述
JVM内存区域内存的物理划分堆、栈、方法区、程序计数器、本地方法栈
JMM内存的访问规则定义线程与主内存之间的抽象关系

jvm-memory-areas-vs-jmm.drawio.png

1.3 JMM的核心概念

1.3.1 八种内存交互操作

JMM定义了8种原子操作来完成主内存与工作内存之间的交互:

jmm-memory-operations.drawio.png

操作作用域描述
lock(锁定)主内存把变量标识为线程独占状态
unlock(解锁)主内存释放处于锁定状态的变量
read(读取)主内存把变量值从主内存传输到工作内存
load(载入)工作内存把read操作得到的值放入工作内存的变量副本中
use(使用)工作内存把工作内存中变量值传递给执行引擎
assign(赋值)工作内存把执行引擎收到的值赋给工作内存的变量
store(存储)工作内存把工作内存中的变量值传送到主内存
write(写入)主内存把store操作得到的值放入主内存的变量中

操作流程示例:

// 线程A执行: sharedVar = 100;

// 1. assign: 将100赋值到工作内存中的sharedVar副本
// 2. store:  将工作内存中sharedVar的值传送到主内存
// 3. write:  将store传送的值写入主内存的sharedVar变量

// 线程B读取: int localVar = sharedVar;

// 1. read: 从主内存读取sharedVar的值
// 2. load: 将read读取的值放入工作内存的sharedVar副本
// 3. use:  将工作内存中sharedVar的值传递给执行引擎

1.3.2 JMM的规则约束

JMM对这8种操作制定了严格的规则:

  1. read和load、store和write必须成对出现
  2. 不允许线程丢弃它最近的assign操作
  3. 不允许线程无原因地把数据从工作内存同步回主内存
  4. 新变量只能在主内存中诞生
  5. 一个变量同一时刻只允许一个线程对其进行lock操作
  6. 对变量执行lock操作,会清空工作内存中该变量的值
  7. 不允许对未被lock的变量执行unlock操作
  8. 对变量执行unlock前,必须先把此变量同步回主内存

1.4 JMM的三大特性

JMM围绕着三个核心特性展开:原子性可见性有序性

1.4.1 原子性(Atomicity)

定义:一个操作或多个操作要么全部执行完成且不被打断,要么都不执行。

JMM保证的原子性操作:

  • 基本类型的读取和赋值操作(注意:long和double的64位操作在32位JVM上可能非原子)
  • synchronized块中的操作
// 原子操作
int a = 10;         // 原子操作
int b = a;          // 非原子操作:包含read a、assign b两步

// 非原子操作
i++;                // 非原子操作:read、inc、write三步
i = i + 1;          // 非原子操作:read、add、write三步

// 使用synchronized保证原子性
synchronized (lock) {
    i++;            // 在synchronized块中变为原子操作
}

// 使用Atomic类保证原子性
AtomicInteger atomicI = new AtomicInteger(0);
atomicI.incrementAndGet();  // 原子操作

1.4.2 可见性(Visibility)

定义:当一个线程修改了共享变量的值,其他线程能够立即得知这个修改。

导致可见性问题的原因:

public class VisibilityProblem {
    private static boolean running = true;
    
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            int count = 0;
            while (running) {  // 可能永远读取的是工作内存中的旧值
                count++;
            }
            System.out.println("Stopped at count: " + count);
        });
        
        t.start();
        Thread.sleep(1000);
        running = false;  // 主线程修改了running,但子线程可能看不到
        System.out.println("Main thread set running = false");
    }
}
// 结果:子线程可能永远不会停止!

保证可见性的方式:

方式原理
volatile强制从主内存读取,修改后立即写回主内存
synchronized解锁前将工作内存同步到主内存
final一旦初始化完成,其他线程就能看到
Lock与synchronized类似
// 使用volatile解决可见性问题
private static volatile boolean running = true;

1.4.3 有序性(Ordering)

定义:程序执行的顺序按照代码的先后顺序执行。

重排序示例:

public class ReorderingExample {
    private static int x = 0, y = 0;
    private static int a = 0, b = 0;
    
    public static void main(String[] args) throws InterruptedException {
        int count = 0;
        while (true) {
            count++;
            x = y = a = b = 0;
            
            Thread t1 = new Thread(() -> {
                a = 1;  // 语句1
                x = b;  // 语句2
            });
            
            Thread t2 = new Thread(() -> {
                b = 1;  // 语句3
                y = a;  // 语句4
            });
            
            t1.start();
            t2.start();
            t1.join();
            t2.join();
            
            // 如果没有重排序,x和y不可能同时为0
            // 但由于重排序的存在,可能出现x=0且y=0的情况!
            if (x == 0 && y == 0) {
                System.out.println("Reordering detected at iteration: " + count);
                break;
            }
        }
    }
}

保证有序性的方式:

方式原理
volatile禁止指令重排序
synchronized同一时刻只有一个线程执行,相当于单线程
Happens-Before规则JMM定义的天然有序性规则

第二篇:进阶篇 —— 深入JMM底层机制

2.1 Happens-Before规则详解

Happens-Before是JMM的核心概念,它定义了操作之间的内存可见性。

2.1.1 什么是Happens-Before

定义:如果操作A happens-before 操作B,那么A的执行结果将对B可见,且A的执行顺序排在B之前。

注意:Happens-Before关系并不意味着A一定要在B之前执行,而是A的结果对B可见。

happens-before-rules.drawio.png

2.1.2 八大Happens-Before规则

规则描述示例
程序顺序规则同一线程中,按照程序代码顺序,前面的操作happens-before后面的操作int a=1; int b=a;
监视器锁规则unlock操作happens-before于后续对同一个锁的lock操作synchronized块
volatile变量规则volatile变量的写操作happens-before于后续对该变量的读操作volatile写→读
传递性规则如果A happens-before B,B happens-before C,那么A happens-before C推导关系
线程启动规则Thread对象的start()方法happens-before于此线程的每一个动作thread.start()
线程终止规则线程中的所有操作happens-before于对此线程的终止检测thread.join()
线程中断规则对线程interrupt()方法的调用happens-before于被中断线程检测到中断事件的发生thread.interrupt()
对象终结规则一个对象的初始化完成happens-before于它的finalize()方法的开始构造函数→finalize

2.1.3 Happens-Before规则详细解析

规则1:程序顺序规则(Program Order Rule)

// 同一线程内
int a = 1;       // 操作A
int b = 2;       // 操作B
int c = a + b;   // 操作C

// A happens-before B
// B happens-before C
// 传递性:A happens-before C

规则2:监视器锁规则(Monitor Lock Rule)

synchronized (this) {  // lock操作
    // 临界区代码
}  // unlock操作

// unlock happens-before 于后续的lock
// 这保证了释放锁之前的所有操作对获取同一把锁的线程可见

规则3:volatile变量规则(Volatile Variable Rule)

volatile boolean flag = false;
int data = 0;

// 线程A
data = 42;          // 操作1
flag = true;        // 操作2(volatile写)

// 线程B
if (flag) {         // 操作3(volatile读)
    int result = data;  // 操作4:此时data必为42
}

// 根据volatile规则:操作2 happens-before 操作3
// 根据程序顺序规则:操作1 happens-before 操作2,操作3 happens-before 操作4
// 根据传递性:操作1 happens-before 操作4
// 因此:线程B读取data时,必定能看到线程A对data的修改

规则4:传递性规则(Transitivity Rule)

// 如果 A happens-before B
// 且 B happens-before C
// 则 A happens-before C

int x = 0;
volatile boolean v = false;
int y = 0;

// 线程1
x = 42;         // A
v = true;       // B(volatile写)

// 线程2
if (v) {        // C(volatile读)
    y = x;      // D: y必为42,因为A h-b B h-b C h-b D
}

2.2 内存屏障(Memory Barrier)

内存屏障是JMM实现Happens-Before规则的底层机制。

2.2.1 什么是内存屏障

内存屏障(Memory Barrier/Memory Fence) 是一组CPU指令,用于:

  1. 阻止屏障两侧的指令重排序
  2. 强制刷新处理器缓存/使缓存失效

2.2.2 四种内存屏障类型

memory-barriers.drawio.png

屏障类型指令示例说明
LoadLoadLoad1; LoadLoad; Load2确保Load1的数据装载先于Load2及后续所有装载指令
StoreStoreStore1; StoreStore; Store2确保Store1的数据对其他处理器可见先于Store2及后续所有存储指令
LoadStoreLoad1; LoadStore; Store2确保Load1的数据装载先于Store2及后续所有存储指令刷新到内存
StoreLoadStore1; StoreLoad; Load2确保Store1的数据对其他处理器可见先于Load2及后续所有装载指令(开销最大,全能屏障)

2.2.3 不同CPU架构的内存屏障支持

架构LoadLoadLoadStoreStoreStoreStoreLoad
x86/x64不需要不需要不需要需要(mfence/lock指令)
ARM需要需要需要需要
PowerPC需要需要需要需要

x86相对"友好":x86架构采用较强的内存模型(TSO),只有StoreLoad可能乱序,因此只需要StoreLoad屏障。

2.3 volatile的实现原理

2.3.1 volatile的内存语义

volatile的两层语义:

  1. 可见性:对volatile变量的读写直接操作主内存
  2. 有序性:禁止volatile变量与普通变量之间的重排序

2.3.2 volatile的内存屏障策略

JMM针对volatile变量的读写,规定了以下内存屏障插入策略:

// volatile写操作
StoreStore屏障
volatile写
StoreLoad屏障

// volatile读操作
volatile读
LoadLoad屏障
LoadStore屏障

图示:

volatile-barrier-flow.drawio.png

2.3.3 volatile在x86下的汇编实现

// Java代码
volatile int v = 0;
v = 10;

// 对应的x86汇编(简化)
movl $10, v(%rip)     // 将10写入v
lock addl $0, (%rsp)  // lock前缀指令,起到内存屏障作用

lock指令前缀的作用:

  1. 锁定总线/缓存行,阻止其他CPU访问
  2. 将当前CPU缓存行的数据写回主内存
  3. 使其他CPU的相关缓存行失效

2.3.4 volatile的使用场景与限制

适用场景:

// 1. 状态标志
volatile boolean shutdownRequested;

public void shutdown() { shutdownRequested = true; }
public void doWork() {
    while (!shutdownRequested) {
        // do work
    }
}

// 2. 一次性安全发布(配合不变性)
volatile Object config;

public void initialize() {
    config = new ImmutableConfig();  // 一次性发布
}

// 3. 独立观察(无依赖的状态)
volatile int temperature;

public void updateTemperature(int temp) {
    temperature = temp;  // 独立更新
}

不适用场景(需要原子性):

// 错误示例:volatile不保证原子性
volatile int count = 0;

public void increment() {
    count++;  // 非原子操作,会有并发问题!
}

// 正确做法
AtomicInteger count = new AtomicInteger(0);
public void increment() {
    count.incrementAndGet();
}

2.4 synchronized的内存语义

2.4.1 synchronized的内存语义

进入synchronized块(lock操作):

  1. 清空工作内存中共享变量的值
  2. 从主内存重新读取共享变量的最新值

退出synchronized块(unlock操作):

  1. 将工作内存中共享变量的修改刷新到主内存
// synchronized的内存语义等效于
class SynchronizedExample {
    int a = 0;
    boolean flag = false;
    
    public synchronized void writer() {  // 获取锁
        a = 1;
        flag = true;
    }  // 释放锁:将a和flag刷新到主内存
    
    public synchronized void reader() {  // 获取锁:从主内存读取a和flag
        if (flag) {
            int i = a;  // i必定为1
        }
    }  // 释放锁
}

2.4.2 synchronized的happens-before关系

synchronized-hb.drawio.png

线程1释放锁的动作 happens-before 线程2获取锁的动作。

2.5 final的内存语义

2.5.1 final字段的重排序规则

写final字段的重排序规则:

  1. JMM禁止把final字段的写重排序到构造函数之外
  2. 在构造函数内对final字段的写入,与随后把构造对象的引用赋值给引用变量,这两个操作不能重排序

读final字段的重排序规则:

  1. 在一个线程中,初次读对象引用与初次读该对象包含的final字段,JMM禁止这两个操作重排序

2.5.2 final字段示例

public class FinalExample {
    final int x;
    int y;
    static FinalExample obj;
    
    public FinalExample() {
        x = 1;  // 写final字段
        y = 2;  // 写普通字段
    }
    
    public static void writer() {
        obj = new FinalExample();
    }
    
    public static void reader() {
        FinalExample o = obj;
        if (o != null) {
            int a = o.x;  // 一定能看到x=1
            int b = o.y;  // 可能看到y=0(普通字段可能重排序)
        }
    }
}

2.5.3 final的内存屏障

final-barrier.drawio.png


第三篇:实战篇 —— JMM在实际开发中的应用

3.1 经典并发问题与JMM解决方案

3.1.1 可见性问题:共享标志位

问题代码:

public class VisibilityProblem {
    private boolean stop = false;  // 可见性问题
    
    public void run() {
        while (!stop) {
            // 业务逻辑
        }
    }
    
    public void stop() {
        stop = true;
    }
}

解决方案:

// 方案1:使用volatile
private volatile boolean stop = false;

// 方案2:使用synchronized
private boolean stop = false;
public synchronized boolean isStop() { return stop; }
public synchronized void stop() { stop = true; }

// 方案3:使用AtomicBoolean
private AtomicBoolean stop = new AtomicBoolean(false);

3.1.2 原子性问题:计数器

问题代码:

public class AtomicityProblem {
    private int count = 0;
    
    public void increment() {
        count++;  // 非原子操作
    }
}

解决方案:

// 方案1:使用synchronized
private int count = 0;
public synchronized void increment() {
    count++;
}

// 方案2:使用AtomicInteger(推荐,性能更好)
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
    count.incrementAndGet();
}

// 方案3:使用LongAdder(高并发场景推荐)
private LongAdder count = new LongAdder();
public void increment() {
    count.increment();
}

3.2 双重检查锁定(DCL)问题深度剖析

3.2.1 单例模式的演进

版本1:饿汉式(线程安全,但非懒加载)

public class Singleton1 {
    private static final Singleton1 INSTANCE = new Singleton1();
    
    private Singleton1() {}
    
    public static Singleton1 getInstance() {
        return INSTANCE;
    }
}

版本2:懒汉式(线程不安全)

public class Singleton2 {
    private static Singleton2 instance;
    
    private Singleton2() {}
    
    public static Singleton2 getInstance() {
        if (instance == null) {           // 线程A和线程B同时进入
            instance = new Singleton2();  // 可能创建多个实例
        }
        return instance;
    }
}

版本3:加锁懒汉式(线程安全,性能差)

public class Singleton3 {
    private static Singleton3 instance;
    
    private Singleton3() {}
    
    public static synchronized Singleton3 getInstance() {
        if (instance == null) {
            instance = new Singleton3();
        }
        return instance;  // 每次获取都需要加锁,性能差
    }
}

3.2.2 DCL问题分析

版本4:双重检查锁定(有问题的版本)

public class Singleton4 {
    private static Singleton4 instance;  // 注意:没有volatile
    
    private Singleton4() {}
    
    public static Singleton4 getInstance() {
        if (instance == null) {              // 第一次检查
            synchronized (Singleton4.class) {
                if (instance == null) {      // 第二次检查
                    instance = new Singleton4();  // 问题所在!
                }
            }
        }
        return instance;
    }
}

问题分析: instance = new Singleton4() 这行代码实际上包含三个步骤:

memory = allocate();     // 1. 分配对象的内存空间
ctorInstance(memory);    // 2. 初始化对象
instance = memory;       // 3. 将instance指向刚分配的内存地址

由于重排序,步骤2和步骤3可能会被重排:

memory = allocate();     // 1. 分配对象的内存空间
instance = memory;       // 3. 将instance指向内存地址(此时对象还未初始化!)
ctorInstance(memory);    // 2. 初始化对象

DCL失败场景:

dcl-problem.drawio.png

3.2.3 正确的DCL实现

版本5:使用volatile的DCL(正确)

public class Singleton5 {
    private static volatile Singleton5 instance;  // 关键:volatile
    
    private Singleton5() {}
    
    public static Singleton5 getInstance() {
        if (instance == null) {
            synchronized (Singleton5.class) {
                if (instance == null) {
                    instance = new Singleton5();
                    // volatile写操作会插入StoreStore和StoreLoad屏障
                    // 禁止了步骤2和步骤3的重排序
                }
            }
        }
        return instance;
    }
}

版本6:使用静态内部类(推荐)

public class Singleton6 {
    private Singleton6() {}
    
    // 静态内部类在首次使用时才会被加载
    private static class SingletonHolder {
        private static final Singleton6 INSTANCE = new Singleton6();
    }
    
    public static Singleton6 getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

版本7:使用枚举(最佳实践)

public enum Singleton7 {
    INSTANCE;
    
    public void doSomething() {
        // 业务方法
    }
}

// 使用
Singleton7.INSTANCE.doSomething();

3.3 伪共享(False Sharing)问题

3.3.1 什么是伪共享

伪共享发生在多个线程同时修改在同一个缓存行(Cache Line)中的不同变量时。

false-sharing.drawio.png

3.3.2 伪共享问题示例

public class FalseSharingDemo implements Runnable {
    public final static int NUM_THREADS = 4;
    public final static long ITERATIONS = 500_000_000L;
    
    private final int arrayIndex;
    private static VolatileLong[] longs = new VolatileLong[NUM_THREADS];
    
    static {
        for (int i = 0; i < longs.length; i++) {
            longs[i] = new VolatileLong();
        }
    }
    
    public FalseSharingDemo(int arrayIndex) {
        this.arrayIndex = arrayIndex;
    }
    
    // 存在伪共享问题的类
    public static class VolatileLong {
        public volatile long value = 0L;  // 8字节,多个VolatileLong可能在同一缓存行
    }
    
    @Override
    public void run() {
        long i = ITERATIONS;
        while (--i > 0) {
            longs[arrayIndex].value = i;  // 不同线程修改不同的VolatileLong
        }
    }
}

3.3.3 解决伪共享的方案

方案1:缓存行填充(Cache Line Padding)

// Java 7及之前
public class PaddedVolatileLong {
    // 填充56字节,加上value的8字节,正好64字节(一个缓存行)
    public long p1, p2, p3, p4, p5, p6, p7;  // 7 * 8 = 56字节
    public volatile long value = 0L;          // 8字节
}

// 或者两边都填充
public class PaddedVolatileLong2 {
    public long p1, p2, p3, p4, p5, p6, p7;
    public volatile long value = 0L;
    public long p8, p9, p10, p11, p12, p13, p14;
}

方案2:使用@Contended注解(Java 8+)

// 需要JVM参数: -XX:-RestrictContended
import sun.misc.Contended;

public class ContendedVolatileLong {
    @Contended
    public volatile long value = 0L;
}

// 或者类级别
@Contended
public class ContendedClass {
    public volatile long value = 0L;
}

JDK中的实际应用:

// ConcurrentHashMap中的CounterCell
@sun.misc.Contended 
static final class CounterCell {
    volatile long value;
    CounterCell(long x) { value = x; }
}

// Thread类中的threadLocalRandomSeed
@sun.misc.Contended("tlr")
long threadLocalRandomSeed;

3.4 JMM在主流框架中的应用

3.4.1 ConcurrentHashMap中的JMM应用

// JDK 8 ConcurrentHashMap部分源码
public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
    implements ConcurrentMap<K,V>, Serializable {
    
    // 使用volatile保证可见性
    transient volatile Node<K,V>[] table;
    
    // 使用Unsafe进行CAS操作,保证原子性
    static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
                                        Node<K,V> c, Node<K,V> v) {
        return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
    }
    
    // 使用volatile读保证可见性
    static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
        return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
    }
    
    // Node节点的val和next都是volatile
    static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        volatile V val;
        volatile Node<K,V> next;
    }
}

3.4.2 AQS(AbstractQueuedSynchronizer)中的JMM应用

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer {
    
    // 核心状态,使用volatile保证可见性
    private volatile int state;
    
    // CAS修改state,保证原子性
    protected final boolean compareAndSetState(int expect, int update) {
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }
    
    // volatile读
    protected final int getState() {
        return state;
    }
    
    // volatile写
    protected final void setState(int newState) {
        state = newState;
    }
}

3.4.3 Spring框架中的JMM应用

// Spring的DefaultSingletonBeanRegistry
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry 
    implements SingletonBeanRegistry {
    
    // 使用ConcurrentHashMap保证线程安全
    private final Map<String, Object> singletonObjects = 
        new ConcurrentHashMap<>(256);
    
    // 双重检查锁定模式获取单例
    public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
        synchronized (this.singletonObjects) {
            Object singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {
                // ... 创建bean的逻辑
                singletonObject = singletonFactory.getObject();
                addSingleton(beanName, singletonObject);
            }
            return singletonObject;
        }
    }
}

总结与最佳实践

JMM核心要点回顾

jmm-knowledge-system.drawio.png

最佳实践清单

场景推荐方案说明
状态标志volatile简单高效
计数器AtomicInteger/LongAdder无锁高并发
复合操作synchronized/Lock保证原子性
单例模式枚举或静态内部类简洁安全
不可变对象final + 不可变类设计天然线程安全
高并发容器JUC并发容器专业实现

常见陷阱提醒

  1. volatile不保证原子性i++需要使用AtomicInteger
  2. DCL必须使用volatile:否则可能获取到未初始化完成的对象
  3. final字段的正确发布:不要在构造函数中让this引用逸出
  4. 伪共享的隐藏性能问题:高并发场景注意缓存行填充
  5. 锁的粒度选择:过细增加开销,过粗降低并发度

参考资料

  1. 《Java并发编程的艺术》 - 方腾飞、魏鹏、程晓明
  2. 《深入理解Java虚拟机》 - 周志明
  3. JSR-133: Java Memory Model and Thread Specification
  4. Doug Lea's JSR-133 Cookbookgee.cs.oswego.edu/dl/jmm/cook…
  5. Intel® 64 and IA-32 Architectures Software Developer's Manual