JMM与内存可见性

一、Java 内存模型(JMM)概述

1.1 什么是 JMM?

定义:Java Memory Model(JMM)是 Java 虚拟机规范定义的一种内存模型,用于屏蔽各种硬件和操作系统的内存访问差异,实现跨平台的内存访问一致性。

核心目标

  • 定义程序中变量的访问规则
  • 保证多线程环境下的内存可见性
  • 规定指令重排序的边界

⚠️ 注意区分

  • JVM 内存结构:堆、栈、方法区等物理内存布局
  • JMM:线程间通信的抽象模型(规范)

1.2 JMM 抽象模型

┌─────────────────────────────────────────────────┐

│              主内存(Main Memory)                │

│  所有线程共享的变量存储区域                        │

│  [对象实例] [静态变量] [常量]                      │

└────────┬──────────────────────────┬──────────────┘

         │                          │

    读取 │ 写入                 读取 │ 写入

         ↓                          ↓

┌────────────────────┐    ┌────────────────────┐

│  线程 A 工作内存      │    │  线程 B 工作内存      │

│  [变量副本]          │    │  [变量副本]          │

│  [CPU 缓存]          │    │  [CPU 缓存]          │

└────────────────────┘    └────────────────────┘

关键概念

术语 说明 对应硬件
主内存 所有线程共享的内存区域 RAM(主存)
工作内存 每个线程私有的内存区域 CPU 缓存(L1/L2/L3)+ 寄存器
变量副本 线程从主内存读取变量到工作内存 缓存行(Cache Line)

1.3 JMM 定义的 8 种操作

JMM 规定了主内存与工作内存之间的交互协议:

操作 作用位置 说明
lock 主内存 锁定变量(独占访问)
unlock 主内存 解锁变量
read 主内存 读取变量到工作内存
load 工作内存 将 read 的值放入工作内存副本
use 工作内存 将工作内存变量传递给执行引擎
assign 工作内存 将执行引擎的值赋给工作内存变量
store 工作内存 将工作内存变量传输到主内存
write 主内存 将 store 的值写入主内存

示例流程

// 线程 A 执行:x = 1;
1. assign: 工作内存 x_副本 = 1
2. store:  将 x_副本 传输到主内存
3. write:  主内存 x = 1

// 线程 B 执行:int y = x;
1. read:   从主内存读取 x
2. load:   加载到工作内存 x_副本
3. use:    将 x_副本 赋值给 y

二、内存可见性问题

2.1 经典案例:循环不停止

public class VisibilityProblem {
    private static boolean flag = false;  // ⚠️ 问题变量
    
    public static void main(String[] args) throws InterruptedException {
        // 线程 A:读取 flag
        Thread threadA = new Thread(() -> {
            while (!flag) {
                // 死循环
            }
            System.out.println("线程 A 结束");
        });
        
        // 线程 B:修改 flag
        Thread threadB = new Thread(() -> {
            flag = true;
            System.out.println("线程 B 已将 flag 设置为 true");
        });
        
        threadA.start();
        Thread.sleep(100);
        threadB.start();
    }
}

// 运行结果:
// 线程 B 已将 flag 设置为 true
// (线程 A 永远不会结束,死循环)

问题分析

时间线:
t1: 线程 A 从主内存读取 flag = false 到工作内存
t2: 线程 A 进入循环,一直使用工作内存的 flag(falset3: 线程 B 修改主内存 flag = true
t4: 线程 A 的工作内存未感知到变化,继续使用旧值 false

根本原因:CPU 缓存导致的可见性问题

硬件层面解释

CPU 架构:
┌─────────┐     ┌─────────┐
│ Core 0  │     │ Core 1  │
│ L1 Cache│     │ L1 Cache│
│ flag=0  │     │ flag=1  │  ← 各自缓存不一致
└────┬────┘     └────┬────┘
     └───────┬───────┘
         L3 Cache
             │
         主内存 RAM

2.2 解决方案对比

方案 代码示例 性能 适用场景
1. volatile volatile boolean flag 状态标志、单次赋值
2. synchronized synchronized(lock) { flag = true; } 需要原子性保证
3. AtomicBoolean AtomicBoolean flag = new AtomicBoolean() CAS 操作
4. final final boolean flag = true 极高 不可变对象

最佳实践(使用 volatile)

public class VisibilityFixed {
    private static volatile boolean flag = false;  // ✅ 添加 volatile
    
    public static void main(String[] args) throws InterruptedException {
        Thread threadA = new Thread(() -> {
            while (!flag) {
                // 可以正常退出
            }
            System.out.println("线程 A 结束");
        });
        
        Thread threadB = new Thread(() -> {
            flag = true;
            System.out.println("线程 B 已将 flag 设置为 true");
        });
        
        threadA.start();
        Thread.sleep(100);
        threadB.start();
    }
}

// 运行结果:
// 线程 B 已将 flag 设置为 true
// 线程 A 结束  ✅

2.3 可见性问题的三种表现

表现 1:读取不到最新值(上述案例)

表现 2:读取到部分更新的值

public class PartialVisibility {
    private static long value = 0L;  // 64 位(非原子操作)
    
    public static void main(String[] args) {
        // 线程 A:写入 Long.MAX_VALUE
        new Thread(() -> {
            value = Long.MAX_VALUE;  // 0x7FFFFFFFFFFFFFFF
        }).start();
        
        // 线程 B:读取 value
        new Thread(() -> {
            long v = value;
            System.out.println(v);  
            // 可能输出:4294967295(只读到低 32 位)⚠️
        }).start();
    }
}

// 原因:
// 在 32 位系统上,long 的读写分为两步:
// 1. 写入低 32 位
// 2. 写入高 32 位
// 线程 B 可能在步骤 1 和 2 之间读取

表现 3:指令重排序导致的问题(见第三节)


三、重排序

3.1 什么是重排序?

定义:编译器和处理器为了优化程序性能,对指令执行顺序进行调整。

三种重排序

源代码
  ↓
编译器优化重排序(javac / JIT)
  ↓
指令级并行重排序(CPU)
  ↓
内存系统重排序(缓存、写缓冲)
  ↓
最终执行顺序

3.2 重排序示例

示例 1:编译器重排序

// 原始代码
int a = 1;
int b = 2;
int c = a + b;

// 可能重排为(c 不依赖 b):
int a = 1;
int c = a + ...; // 先计算部分
int b = 2;
int c = a + b;

示例 2:CPU 指令重排序

// 原始代码
a = 1;  // 写操作 1
b = 2;  // 写操作 2

// CPU 可能重排为(写缓冲优化):
b = 2;  // 先写 b(缓冲区有空闲)
a = 1;  // 后写 a

3.3 重排序的经典问题:双重检查锁(DCL)

public class Singleton {
    private static Singleton instance;  // ⚠️ 问题:未加 volatile
    
    public static Singleton getInstance() {
        if (instance == null) {              // 检查 1
            synchronized (Singleton.class) {
                if (instance == null) {      // 检查 2
                    instance = new Singleton();  // ⚠️ 关键代码
                }
            }
        }
        return instance;
    }
}

问题分析:

// instance = new Singleton() 实际分为 3 步:
1. memory = allocate();   // 分配内存
2. ctorInstance(memory);  // 初始化对象
3. instance = memory;     // 设置引用

// ⚠️ 可能重排序为:
1. memory = allocate();
3. instance = memory;     // 先设置引用(此时对象未初始化)
2. ctorInstance(memory);  // 后初始化

// 线程 A 执行到步骤 3
// 线程 B 执行检查 1,发现 instance != null
// 线程 B 返回 instance(未初始化的对象)→ 使用时 NPE

执行时序图

线程 A                          线程 B
──────────────────────────────────────────
分配内存
instance = memory ────────→    if (instance == null)  ✗
                               return instance; ⚠️ 半初始化对象
初始化对象(太晚了)

正确写法:

public class Singleton {
    private static volatile Singleton instance;  // ✅ 添加 volatile
    
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();  // volatile 禁止重排序
                }
            }
        }
        return instance;
    }
}

3.4 as-if-serial 语义

定义:不管怎么重排序,单线程程序的执行结果不能改变。

// 示例:
int a = 1;
int b = 2;
int c = a + b;

// 重排序规则:
// ✅ 允许:a = 1; int b = 2; 交换顺序(无依赖)
// ❌ 禁止:c = a + b; 移到 a = 1 之前(有依赖)

// 保证单线程结果:c = 3

数据依赖性

写后读(RAW):a = 1; b = a;  // b 依赖 a
写后写(WAW):a = 1; a = 2;  // 第二个写覆盖第一个
读后写(WAR):b = a; a = 1;  // a 的新值不能影响 b 的旧读取

⚠️ 有数据依赖的操作不会重排序(单线程内)

四、happens-before 原则

4.1 定义

happens-before:JMM 定义的操作偏序关系,如果 A happens-before B,则 A 的结果对 B 可见。

核心作用

  • 程序员:不需要理解底层重排序,只需遵循 happens-before 规则
  • JVM:在不违反 happens-before 的前提下,可以自由优化

4.2 八大 happens-before 规则

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

定义:单线程内,按照代码顺序执行。

int a = 1;  // 操作 A
int b = 2;  // 操作 B
int c = a + b;  // 操作 C

// happens-before 关系:
// A happens-before B
// B happens-before C
// 结论:C 读取的 a 和 b 一定是最新值

⚠️ 注意:允许重排序,但必须保证结果一致(as-if-serial)

int a = 1;
int b = 2;  // 可能与 a 重排序(无依赖)
int c = a + b;  // 但 c 一定在 a, b 之后

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

定义:对 volatile 变量的写操作 happens-before 后续对该变量的读操作。

public class VolatileExample {
    private volatile boolean flag = false;
    private int data = 0;
    
    // 线程 A
    public void writer() {
        data = 42;         // ① 普通写
        flag = true;       // ② volatile 写
    }
    
    // 线程 B
    public void reader() {
        if (flag) {        // ③ volatile 读
            int x = data;  // ④ 普通读
            // x 一定等于 42 ✅
        }
    }
}

// happens-before 链:
// ① happens-before ②(程序顺序规则)
// ② happens-before ③(volatile 规则)
// ③ happens-before ④(程序顺序规则)
// 结论:① happens-before ④(传递性)

内存语义图

线程 A                          线程 B
──────────────────────────────────────────
data = 42
flag = true (volatile 写)
  │                           
  │  volatile 内存屏障         
  └────────────────────→   if (flag) volatile 读
                              int x = data; ✅ 看到 42

规则 3:锁规则(Monitor Lock Rule)

定义:对锁的解锁 happens-before 后续对该锁的加锁。

public class SynchronizedExample {
    private int data = 0;
    
    // 线程 A
    public synchronized void writer() {
        data = 42;  // ① 写操作
    }  // ② 解锁
    
    // 线程 B
    public synchronized void reader() {  // ③ 加锁
        int x = data;  // ④ 读操作
        // x 一定等于 42 ✅
    }
}

// happens-before 链:
// ① happens-before ②(程序顺序规则)
// ② happens-before ③(锁规则)
// ③ happens-before ④(程序顺序规则)
// 结论:① happens-before ④

图示

线程 A              Monitor              线程 B
─────────────────────────────────────────────────
data = 42
unlock ────────→ [锁释放] ────────→ lock
                                      int x = data; ✅

规则 4:线程启动规则(Thread Start Rule)

定义:Thread.start() happens-before 该线程的任何操作。

public class ThreadStartExample {
    private int data = 0;
    
    public static void main(String[] args) {
        ThreadStartExample obj = new ThreadStartExample();
        obj.data = 42;  // ① 主线程写
        
        Thread thread = new Thread(() -> {
            int x = obj.data;  // ③ 子线程读
            // x 一定等于 42 ✅
        });
        
        thread.start();  // ② start() 操作
    }
}

// happens-before 链:
// ① happens-before ②(程序顺序规则)
// ② happens-before ③(线程启动规则)
// 结论:① happens-before ③

规则 5:线程终止规则(Thread Termination Rule)

定义:线程的所有操作 happens-before 其他线程检测到该线程终止(join() 返回)。

public class ThreadJoinExample {
    private int data = 0;
    
    public static void main(String[] args) throws InterruptedException {
        ThreadJoinExample obj = new ThreadJoinExample();
        
        Thread thread = new Thread(() -> {
            obj.data = 42;  // ① 子线程写
        });
        
        thread.start();
        thread.join();  // ② 等待子线程结束
        
        int x = obj.data;  // ③ 主线程读
        // x 一定等于 42 ✅
    }
}

// happens-before 链:
// ① happens-before ②(线程终止规则)
// ② happens-before ③(程序顺序规则)
// 结论:① happens-before ③

规则 6:线程中断规则(Thread Interruption Rule)

定义:对线程 interrupt() 的调用 happens-before 被中断线程检测到中断事件。

public class InterruptExample {
    private int data = 0;
    
    public static void main(String[] args) throws InterruptedException {
        InterruptExample obj = new InterruptExample();
        
        Thread thread = new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                // 工作中...
            }
            int x = obj.data;  // ③ 读取
            // x 一定能看到主线程的修改 ✅
        });
        
        thread.start();
        obj.data = 42;  // ① 主线程写
        thread.interrupt();  // ② 中断
    }
}

// happens-before 链:
// ① happens-before ②(程序顺序规则)
// ② happens-before ③(线程中断规则)
// 结论:① happens-before ③

规则 7:对象终结规则(Finalizer Rule)

定义:对象的构造函数结束 happens-before finalize() 方法的开始。

public class FinalizerExample {
    private int data;
    
    public FinalizerExample() {
        data = 42;  // ① 构造函数
    }  // ② 构造结束
    
    @Override
    protected void finalize() {  // ③ finalize 开始
        int x = data;  // ④ 读取
        // x 一定等于 42 ✅
    }
}

// happens-before 链:
// ① happens-before ②(程序顺序规则)
// ② happens-before ③(对象终结规则)
// ③ happens-before ④(程序顺序规则)

⚠️ 注意:finalize() 已被废弃(JDK 9+),不推荐使用。


规则 8:传递性规则(Transitivity)

定义:如果 A happens-before B,B happens-before C,则 A happens-before C。

public class TransitivityExample {
    private volatile boolean ready = false;
    private int data = 0;
    
    // 线程 A
    public void init() {
        data = 42;      // ① 写 data
        ready = true;   // ② volatile 写
    }
    
    // 线程 B
    public void use() {
        if (ready) {    // ③ volatile 读
            int x = data;  // ④ 读 data
            // x 一定等于 42 ✅
        }
    }
}

// happens-before 链:
// ① happens-before ②(程序顺序规则)
// ② happens-before ③(volatile 规则)
// ③ happens-before ④(程序顺序规则)
// 通过传递性:① happens-before ④ ✅

4.3 happens-before 与时间顺序的关系

⚠️ 重要:happens-before 不等于时间先后顺序!

int a = 1;  // ① 
int b = 2;  // ②

// ① happens-before ②(程序顺序规则)
// 但实际执行可能:② 先执行,① 后执行(重排序)
// 只要保证:如果 ② 依赖 ①,则 ① 的结果对 ② 可见

示例:允许重排序的情况

public class ReorderExample {
    private int a = 0;
    private int b = 0;
    
    public void method() {
        a = 1;  // ① 
        b = 2;  // ②
        int c = a + b;  // ③
    }
}

// happens-before 关系:
// ① happens-before ③
// ② happens-before ③
// 但 ① 和 ② 之间无 happens-before 关系 → 可以重排序

// 可能的执行顺序:
// b = 2; a = 1; int c = a + b;  ✅ 合法

五、volatile 关键字

5.1 volatile 的两大特性

特性 说明 示例
可见性 写操作立即刷新到主内存,读操作从主内存读取 状态标志
有序性 禁止指令重排序(内存屏障) 双重检查锁

⚠️ 不保证原子性

private volatile int count = 0;

count++;  // ❌ 非原子操作(读-改-写)
// 等价于:
// int temp = count;  // 读
// temp = temp + 1;   // 改
// count = temp;      // 写
// 多线程下会丢失更新

5.2 volatile 的内存语义

写操作的内存语义

volatile boolean flag = false;

// 写操作:flag = true
1. 将当前线程工作内存中的所有变量刷新到主内存
2. 写入 flag = true 到主内存
3. 插入 StoreLoad 屏障(禁止后续读操作重排序到写之前)

读操作的内存语义

// 读操作:if (flag)
1. 插入 LoadLoad 屏障(禁止后续读操作重排序到 flag 读取之前)
2. 从主内存读取 flag 的最新值
3. 清空工作内存,后续读取从主内存加载

5.3 volatile 的内存屏障

四种内存屏障

屏障类型 指令示例 说明
LoadLoad Load1; LoadLoad; Load2 Load1 的数据必须先加载
StoreStore Store1; StoreStore; Store2 Store1 的数据必须先刷新
LoadStore Load1; LoadStore; Store2 Load1 必须先完成
StoreLoad Store1; StoreLoad; Load2 Store1 必须全局可见后才能 Load2

volatile 写操作插入的屏障

普通写/volatile 写
StoreStore 屏障  ← 禁止上面的写与 volatile 写重排序
volatile 写
StoreLoad 屏障   ← 禁止 volatile 写与下面的读重排序
普通读/volatile

volatile 读操作插入的屏障

普通读/volatile 读
LoadLoad 屏障    ← 禁止下面的读与 volatile 读重排序
volatile 读
LoadStore 屏障   ← 禁止下面的写与 volatile 读重排序
普通写/volatile

5.4 volatile 的适用场景

✅ 场景 1:状态标志

public class StatusFlag {
    private volatile boolean shutdown = false;
    
    public void shutdown() {
        shutdown = true;  // 写线程
    }
    
    public void doWork() {
        while (!shutdown) {  // 读线程
            // 执行任务
        }
    }
}

✅ 场景 2:双重检查锁(DCL)

public class Singleton {
    private volatile static Singleton instance;
    
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

✅ 场景 3:独立观察(一写多读)

public class UserManager {
    private volatile User currentUser;
    
    // 写线程(单线程)
    public void updateUser(User user) {
        currentUser = user;
    }
    
    // 读线程(多线程)
    public User getCurrentUser() {
        return currentUser;
    }
}

❌ 场景 4:计数器(需要原子性)

public class Counter {
    private volatile int count = 0;
    
    public void increment() {
        count++;  // ❌ 非原子操作,会丢失更新
    }
    
    // 正确做法:
    private AtomicInteger count = new AtomicInteger(0);
    public void increment() {
        count.incrementAndGet();  // ✅ CAS 保证原子性
    }
}

5.5 volatile vs synchronized

对比项 volatile synchronized
可见性 ✅ 保证 ✅ 保证
原子性 ❌ 不保证 ✅ 保证
有序性 ✅ 保证(禁止重排序) ✅ 保证(锁机制)
性能 高(无锁) 低(加锁开销)
阻塞 不阻塞 阻塞
适用场景 状态标志、单次赋值 复合操作、临界区

六、final 关键字的内存语义

6.1 final 的三种使用场景

使用位置 说明 内存语义
final 变量 基本类型/引用类型 一次赋值,不可修改
final 方法 方法不可重写 无特殊内存语义
final 类 类不可继承 无特殊内存语义

本节重点讨论:final 域(成员变量)的内存语义


6.2 final 域的重排序规则

规则 1:final 域的写入重排序规则

定义:禁止将 final 域的写操作重排序到构造函数之外。

public class FinalExample {
    private final int x;
    private int y;
    private static FinalExample obj;
    
    public FinalExample() {
        x = 1;  // ① final 域写入
        y = 2;  // ② 普通域写入
    }  // ③ 构造函数结束
    
    public static void writer() {
        obj = new FinalExample();  // ④ 对象引用赋值
    }
    
    public static void reader() {
        FinalExample o = obj;  // ⑤ 读取引用
        int a = o.x;  // ⑥ 读取 final 域
        int b = o.y;  // ⑦ 读取普通域
    }
}

重排序分析

// ✅ 允许的重排序(普通域):
obj = new FinalExample();  // 先赋值引用
y = 2;                     // 后初始化 y(⚠️ 可能导致读到 0)

// ❌ 禁止的重排序(final 域):
obj = new FinalExample();  // 先赋值引用
x = 1;                     // 后初始化 x(禁止!)

// 保证:如果读线程看到 obj != null,则 x 一定初始化完成

内存屏障实现(JVM 层面):

构造函数:
    x = 1;  // final 域写入
    y = 2;  // 普通域写入
    StoreStore 屏障  ← 插入内存屏障
} // 构造函数结束
obj = new FinalExample();  // 对象引用赋值

规则 2:final 域的读取重排序规则

定义:禁止初次读取对象引用与读取 final 域重排序。

public static void reader() {
    FinalExample o = obj;  // ① 读取引用
    int a = o.x;           // ② 读取 final 域
}

// ❌ 禁止重排序为:
int a = o.x;           // 先读 x(此时 o 可能为 null)
FinalExample o = obj;  // 后读引用

内存屏障实现

FinalExample o = obj;  // 读取引用
LoadLoad 屏障          ← 插入内存屏障
int a = o.x;           // 读取 final 域

6.3 final 引用类型的特殊规则

场景:final 域引用的对象

public class FinalReferenceExample {
    private final int[] arr;  // final 引用数组
    
    public FinalReferenceExample() {
        arr = new int[10];  // ① 分配数组
        arr[0] = 1;         // ② 初始化数组元素
    }
    
    private static FinalReferenceExample obj;
    
    public static void writer() {
        obj = new FinalReferenceExample();  // ③ 赋值引用
    }
    
    public static void reader() {
        FinalReferenceExample o = obj;  // ④ 读取引用
        int x = o.arr[0];  // ⑤ 读取数组元素
        // x 一定等于 1 ✅
    }
}

额外规则

构造函数内对 final 引用对象的成员域的写入:
    arr = new int[10];
    arr[0] = 1;  ← 这个写入也受 final 保护
    StoreStore 屏障
} // 构造函数结束

// 保证:读线程看到 obj != null 时,arr[0] 一定初始化完成

⚠️ 注意:final 只保护构造函数内的初始化,不保护后续修改!

public class FinalMutableExample {
    private final List<String> list = new ArrayList<>();
    
    public FinalMutableExample() {
        list.add("A");  // ✅ 构造函数内,受保护
    }
    
    public void addElement(String s) {
        list.add(s);  // ❌ 构造函数外,不受 final 保护
        // 需要额外的同步机制
    }
}

6.4 final 的实际应用场景

应用 1:安全发布不可变对象

// 不使用 final(不安全)
public class UnsafePoint {
    private int x;
    private int y;
    
    public UnsafePoint(int x, int y) {
        this.x = x;
        this.y = y;
    }
}

// 问题:
Point p = new Point(1, 2);
// 另一个线程读取 p.x 可能读到 0(重排序导致)

// 使用 final(安全)
public class SafePoint {
    private final int x;  // ✅ final 保证可见性
    private final int y;
    
    public SafePoint(int x, int y) {
        this.x = x;
        this.y = y;
    }
}

// 保证:任何线程读取 p.x 一定读到 1

应用 2:String 类的不可变性

public final class String {
    private final char[] value;  // ✅ final 保证安全发布
    
    public String(String original) {
        this.value = original.value;
        // final 保证其他线程读取 value 一定是初始化后的
    }
}

// 优势:
// 1. 无需同步即可在多线程间共享
// 2. 可以安全地作为 HashMap 的 key
// 3. 字符串常量池优化

应用 3:享元模式(Flyweight)

public final class Integer {
    private final int value;  // ✅ final 不可变
    
    // 缓存 -128 到 127
    private static class IntegerCache {
        static final Integer[] cache = new Integer[256];
        static {
            for (int i = 0; i < cache.length; i++) {
                cache[i] = new Integer(i - 128);
            }
        }
    }
    
    public static Integer valueOf(int i) {
        if (i >= -128 && i <= 127) {
            return IntegerCache.cache[i + 128];  // 安全共享
        }
        return new Integer(i);
    }
}

6.5 final 与 volatile 的对比

对比项 final volatile
可见性 ✅ 构造函数完成后可见 ✅ 每次读写都可见
原子性 N/A(不涉及修改) ❌ 不保证
有序性 ✅ 禁止构造函数重排序 ✅ 禁止读写重排序
修改 ❌ 不可修改 ✅ 可以修改
性能 最高(编译期优化) 高(无锁)
使用场景 不可变对象、常量 状态标志、单次赋值

七、实战案例

案例 1:单例模式的演进

版本 1:懒加载(线程不安全)

public class Singleton {
    private static Singleton instance;
    
    public static Singleton getInstance() {
        if (instance == null) {  // ❌ 多线程下会创建多个实例
            instance = new Singleton();
        }
        return instance;
    }
}

问题

线程 A                          线程 B
──────────────────────────────────────────
if (instance == null) ✓
                                if (instance == null) ✓
instance = new Singleton();
                                instance = new Singleton();
// 创建了两个实例 ❌

版本 2:加锁(性能差)

public class Singleton {
    private static Singleton instance;
    
    public static synchronized Singleton getInstance() {  // ✅ 线程安全
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

问题:每次调用都要加锁,性能差(即使已经初始化)。


版本 3:双重检查锁(DCL,有问题)

public class Singleton {
    private static Singleton instance;  // ⚠️ 缺少 volatile
    
    public static Singleton getInstance() {
        if (instance == null) {  // 检查 1(无锁)
            synchronized (Singleton.class) {
                if (instance == null) {  // 检查 2(加锁)
                    instance = new Singleton();  // ⚠️ 问题代码
                }
            }
        }
        return instance;
    }
}

问题分析(重排序导致):

// instance = new Singleton() 分解为:
1. memory = allocate();   // 分配内存
2. ctorInstance(memory);  // 初始化对象
3. instance = memory;     // 设置引用

// ⚠️ 可能重排序为:
1. memory = allocate();
3. instance = memory;     // 先设置引用(对象未初始化)
2. ctorInstance(memory);  // 后初始化

// 时序图:
线程 A                          线程 B
──────────────────────────────────────────
分配内存
instance = memory ────────→    if (instance == null) ✗
                               return instance; ⚠️ 半初始化对象
初始化对象(太晚了)

版本 4:DCL + volatile(正确)✅

public class Singleton {
    private static volatile Singleton instance;  // ✅ 添加 volatile
    
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();  // volatile 禁止重排序
                }
            }
        }
        return instance;
    }
}

volatile 的作用

// volatile 写操作会插入 StoreStore 屏障:
memory = allocate();
ctorInstance(memory);
StoreStore 屏障  ← 禁止上面的初始化与下面的引用赋值重排序
instance = memory;

// 保证:读线程看到 instance != null 时,对象一定初始化完成

版本 5:静态内部类(推荐)✅

public class Singleton {
    private Singleton() {}
    
    private static class Holder {
        private static final Singleton INSTANCE = new Singleton();
    }
    
    public static Singleton getInstance() {
        return Holder.INSTANCE;  // 类加载时初始化,天然线程安全
    }
}

优势

  • 懒加载:Holder 类在 getInstance() 调用时才加载
  • 线程安全:类加载由 JVM 保证同步
  • 性能好:无锁

版本 6:枚举(最佳实践)✅

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

// 使用:
Singleton.INSTANCE.doSomething();

优势

  • 天然单例(JVM 保证)
  • 线程安全
  • 防止反序列化破坏单例
  • 防止反射攻击

案例 2:生产者-消费者模式(volatile 实现)

public class ProducerConsumer {
    private volatile boolean hasData = false;
    private int data;
    
    // 生产者
    public void produce(int value) {
        data = value;       // ① 写数据
        hasData = true;     // ② volatile 写
    }
    
    // 消费者
    public int consume() {
        while (!hasData) {  // ③ volatile 读
            // 自旋等待
        }
        int result = data;  // ④ 读数据
        hasData = false;
        return result;      // result 一定等于 value ✅
    }
}

// happens-before 链:
// ① happens-before ②(程序顺序规则)
// ② happens-before ③(volatile 规则)
// ③ happens-before ④(程序顺序规则)
// 结论:① happens-before ④(传递性)

案例 3:配置类的安全发布

// 不安全的发布
public class UnsafeConfig {
    private Map<String, String> config;

    public UnsafeConfig() {
        config = new HashMap<>();
        config.put("url", "http://example.com");
        config.put("timeout", "30");
    }

    // 问题:其他线程可能读到未初始化的 config 或部分初始化的 Map
}

// 安全发布(使用 final)
public class SafeConfig {
    private final Map<String, String> config;  // ✅ final 保证可见性

    public SafeConfig() {
        Map<String, String> temp = new HashMap<>();
        temp.put("url", "http://example.com");
        temp.put("timeout", "30");
        config = Collections.unmodifiableMap(temp);  // ✅ 不可变
    }

    public String getConfig(String key) {
        return config.get(key);  // 线程安全,无需同步
    }
}

// 保证:任何线程读取 config 时,Map 已完全初始化

案例 4:延迟初始化占位符(Holder)

public class ResourceFactory {
    // 重量级资源
    private static class ResourceHolder {
        static final Resource resource = new Resource();  // ✅ 类加载时初始化
    }
    
    public static Resource getResource() {
        return ResourceHolder.resource;  // 懒加载 + 线程安全
    }
}

// 类加载时序:
// 1. ResourceFactory 类加载
// 2. 首次调用 getResource() 时,触发 ResourceHolder 类加载
// 3. 初始化 static final resource(JVM 保证线程安全)

案例 5:volatile 数组问题(常见误区)

// ❌ 错误示例
public class VolatileArray {
    private volatile int[] arr = new int[10];
    
    public void writer() {
        arr[0] = 1;  // ⚠️ 数组元素的修改不受 volatile 保护
    }
    
    public int reader() {
        return arr[0];  // ⚠️ 可能读到旧值
    }
}

// 问题分析:
// volatile 只保证 arr 引用的可见性
// 不保证 arr[0] 元素的可见性

// ✅ 正确做法 1:使用 AtomicIntegerArray
public class AtomicArrayExample {
    private AtomicIntegerArray arr = new AtomicIntegerArray(10);
    
    public void writer() {
        arr.set(0, 1);  // ✅ 原子操作 + 可见性
    }
    
    public int reader() {
        return arr.get(0);  // ✅ 读取最新值
    }
}

// ✅ 正确做法 2:使用 synchronized
public class SyncArrayExample {
    private int[] arr = new int[10];
    
    public synchronized void writer() {
        arr[0] = 1;  // ✅ 锁保证可见性
    }
    
    public synchronized int reader() {
        return arr[0];  // ✅ 读取最新值
    }
}

八、面试高频问题

Q1:什么是 Java 内存模型(JMM)?

参考答案

JMM 是 Java 虚拟机规范定义的一种内存模型,用于屏蔽各种硬件和操作系统的内存访问差异。

核心概念

  • 主内存:所有线程共享的内存区域
  • 工作内存:每个线程私有的内存区域(CPU 缓存)
  • 内存交互:定义了 8 种操作(read、load、use、assign、store、write、lock、unlock)

作用

  1. 定义变量的访问规则
  2. 保证多线程环境下的内存可见性
  3. 规定指令重排序的边界(happens-before)

与 JVM 内存结构的区别

  • JVM 内存结构:堆、栈、方法区等物理内存布局
  • JMM:线程间通信的抽象模型(规范)

Q2:什么是内存可见性问题?如何解决?

参考答案

定义:一个线程修改了共享变量的值,其他线程不能立即看到修改后的值。

原因

CPU 缓存架构:

线程 A(Core 0)      线程 B(Core 1)

    ↓                     ↓

  L1 Cache              L1 Cache

  flag=false            flag=false  ← 各自缓存

    └────────┬────────────┘

           L3 Cache

              ↓

         主内存 RAM

         flag=true

解决方案

方案 使用场景 性能
volatile 状态标志、单次赋值
synchronized 需要原子性保证
Lock 复杂同步逻辑
Atomic 类 CAS 操作
final 不可变对象 极高

示例

// 问题代码
private static boolean flag = false;

// 解决方案
private static volatile boolean flag = false;  // ✅

Q3:volatile 有什么作用?能保证原子性吗?

参考答案

作用(两大特性):

  1. 可见性

    • 写操作:立即刷新到主内存
    • 读操作:从主内存读取最新值
  2. 有序性

    • 禁止指令重排序(通过内存屏障)
    • 保证 volatile 变量前后的操作不会被重排序

不保证原子性

private volatile int count = 0;

count++;  // ❌ 非原子操作
// 分解为:
// 1. temp = count;  (读)
// 2. temp = temp + 1;  (改)
// 3. count = temp;  (写)
// 多线程下会丢失更新

// 正确做法:
AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet();  // ✅ CAS 保证原子性

内存屏障实现

volatile 写:
    StoreStore 屏障
    volatile 写
    StoreLoad 屏障

volatile 读:
    LoadLoad 屏障
    volatile 读
    LoadStore 屏障

Q4:什么是重排序?为什么要重排序?

参考答案

定义:编译器和处理器为了优化性能,对指令执行顺序进行调整。

三种重排序

源代码
  ↓
1. 编译器优化重排序(javac/JIT)
  ↓
2. 指令级并行重排序(CPU 流水线)
  ↓
3. 内存系统重排序(缓存、写缓冲)
  ↓
最终执行顺序

为什么要重排序

  • 性能优化:充分利用 CPU 流水线、缓存
  • 减少等待:避免等待慢速内存操作

限制条件

  • as-if-serial:单线程内结果不变
  • 数据依赖性:有依赖的操作不会重排序

示例

int a = 1;  // ①
int b = 2;  // ②
int c = a + b;  // ③

// 允许重排序:① 和 ② 交换(无依赖)
// 禁止重排序:③ 不能移到 ① 或 ② 之前(有依赖)

多线程问题

// 线程 A
x = 1;
flag = true;

// 线程 B
if (flag) {
    int y = x;  // 可能读到 x = 0(重排序导致)
}

Q5:什么是 happens-before 原则?

参考答案

定义:JMM 定义的操作偏序关系,如果 A happens-before B,则 A 的结果对 B 可见。

核心作用

  • 程序员:只需遵循 happens-before 规则,无需理解底层重排序
  • JVM:在不违反 happens-before 的前提下,可以自由优化

八大规则

规则 说明 示例
程序顺序 单线程内按代码顺序执行 a=1; b=2;
volatile 规则 volatile 写 happens-before 后续读 flag = true;
锁规则 解锁 happens-before 后续加锁 synchronized
线程启动 start() happens-before 线程操作 thread.start();
线程终止 线程操作 happens-before join() 返回 thread.join();
线程中断 interrupt() happens-before 检测中断 thread.interrupt();
对象终结 构造结束 happens-before finalize() finalize()
传递性 A→B, B→C ⇒ A→C 传递链

示例

private volatile boolean flag = false;
private int data = 0;

// 线程 A
public void writer() {
    data = 42;      // ① 
    flag = true;    // ② volatile 写
}

// 线程 B
public void reader() {
    if (flag) {     // ③ volatile 读
        int x = data;  // ④
        // x 一定等于 42 ✅
    }
}

// happens-before 链:
// ① → ②(程序顺序)
// ② → ③(volatile 规则)
// ③ → ④(程序顺序)
// 结论:① → ④(传递性)

Q6:双重检查锁(DCL)为什么要用 volatile?

参考答案

问题代码

public class Singleton {
    private static Singleton instance;  // ⚠️ 缺少 volatile
    
    public static Singleton getInstance() {
        if (instance == null) {  // 检查 1
            synchronized (Singleton.class) {
                if (instance == null) {  // 检查 2
                    instance = new Singleton();  // ⚠️ 问题代码
                }
            }
        }
        return instance;
    }
}

问题分析

// instance = new Singleton() 分解为 3 步:
1. memory = allocate();   // 分配内存
2. ctorInstance(memory);  // 初始化对象
3. instance = memory;     // 设置引用

// ⚠️ 可能重排序为:
1. memory = allocate();
3. instance = memory;     // 先设置引用(对象未初始化)
2. ctorInstance(memory);  // 后初始化

// 导致:
线程 A 执行到步骤 3 后,instance != null
线程 B 执行检查 1,发现 instance != null,直接返回
线程 B 使用 instance(未初始化的对象)→ NPE

volatile 的作用:

private static volatile Singleton instance;  // ✅ 添加 volatile

// volatile 写操作插入 StoreStore 屏障:
memory = allocate();
ctorInstance(memory);
StoreStore 屏障  ← 禁止重排序
instance = memory;

// 保证:读线程看到 instance != null 时,对象一定初始化完成

时序图

volatile:
线程 A                          线程 B
──────────────────────────────────────────
分配内存
instance = memory ────────→    if (instance == null) ✗
                               return instance; ⚠️ 未初始化
初始化对象(太晚了)

有 volatile:
线程 A                          线程 B
──────────────────────────────────────────
分配内存
初始化对象
内存屏障
instance = memory ────────→    if (instance == null) ✗
                               return instance; ✅ 已初始化

Q7:volatile 和 synchronized 有什么区别?

参考答案

对比项 volatile synchronized
可见性 ✅ 保证 ✅ 保证
原子性 ❌ 不保证 ✅ 保证
有序性 ✅ 禁止重排序 ✅ 锁机制保证
性能 高(无锁,轻量级) 低(加锁,重量级)
阻塞 不阻塞 阻塞
语义 变量级别 代码块/方法级别
锁升级 有(偏向锁→轻量级锁→重量级锁)

使用场景

// volatile 适用场景:
// 1. 状态标志
private volatile boolean shutdown = false;

// 2. 单次赋值
private volatile User currentUser;

// 3. 独立观察(一写多读)
private volatile long lastUpdateTime;

// synchronized 适用场景:
// 1. 复合操作
public synchronized void increment() {
    count++;  // 读-改-写
}

// 2. 多个变量需要保证一致性
public synchronized void transfer(Account from, Account to, int amount) {
    from.balance -= amount;
    to.balance += amount;
}

// 3. 临界区保护
public void updateCache() {
    synchronized (lock) {
        cache.clear();
        cache.putAll(newData);
    }
}

Q8:final 关键字的内存语义是什么?

参考答案

两大重排序规则

  1. final 域写入规则

    • 禁止将 final 域的写操作重排序到构造函数之外
    • 保证:读线程看到对象引用时,final 域一定初始化完成
  2. final 域读取规则

    • 禁止初次读取对象引用与读取 final 域重排序
    • 保证:读取 final 域前,一定先读取对象引用

实现机制:

public class FinalExample {
    private final int x;
    private int y;
    
    public FinalExample() {
        x = 1;  // final 域写入
        y = 2;  // 普通域写入
        // 编译器在此处插入 StoreStore 屏障
    }  // 构造函数结束
    
    private static FinalExample obj;
    
    public static void writer() {
        obj = new FinalExample();  // 对象引用赋值
    }
    
    public static void reader() {
        FinalExample o = obj;  // 读取引用
        // 编译器在此处插入 LoadLoad 屏障
        int a = o.x;  // 读取 final 域,保证读到 1 ✅
        int b = o.y;  // 读取普通域,可能读到 0 ⚠️
    }
}

final 引用类型的特殊保护

public class FinalReferenceExample {
    private final int[] arr;
    
    public FinalReferenceExample() {
        arr = new int[10];
        arr[0] = 1;  // ✅ 这个写入也受 final 保护
        // StoreStore 屏障
    }
    
    // 保证:读线程看到 arr != null 时,arr[0] 一定等于 1
}

典型应用

// 不可变对象(String)
public final class String {
    private final char[] value;  // ✅ 安全发布
    
    public String(String original) {
        this.value = original.value;
        // 无需额外同步,final 保证可见性
    }
}

// 单例模式(静态内部类)
public class Singleton {
    private static class Holder {
        static final Singleton INSTANCE = new Singleton();  // ✅ 线程安全
    }
}

Q9:如何安全地发布对象?

参考答案

不安全发布示例

public class UnsafePublish {
    private Map<String, String> config;
    
    public UnsafePublish() {
        config = new HashMap<>();
        config.put("key", "value");
        // ⚠️ 另一个线程可能读到:
        // 1. config = null(未初始化)
        // 2. config != null,但 Map 为空(部分初始化)
    }
}

安全发布的四种方式

方式 1:使用 final 关键字

public class SafePublish1 {
    private final Map<String, String> config;  // ✅ final 保证可见性
    
    public SafePublish1() {
        Map<String, String> temp = new HashMap<>();
        temp.put("key", "value");
        config = Collections.unmodifiableMap(temp);  // 不可变
    }
}

方式 2:使用 volatile 关键字

public class SafePublish2 {
    private volatile Map<String, String> config;  // ✅ volatile 保证可见性
    
    public void init() {
        Map<String, String> temp = new HashMap<>();
        temp.put("key", "value");
        config = temp;  // volatile 写
    }
}

方式 3:使用 synchronized 同步

public class SafePublish3 {
    private Map<String, String> config;
    
    public synchronized void init() {  // ✅ 锁保证可见性
        config = new HashMap<>();
        config.put("key", "value");
    }
    
    public synchronized Map<String, String> getConfig() {
        return config;
    }
}

方式 4:使用静态初始化器

public class SafePublish4 {
    // ✅ 类加载时初始化,JVM 保证线程安全
    private static final Map<String, String> config = new HashMap<>();
    
    static {
        config.put("key", "value");
    }
}

总结

安全发布 = 对象完全初始化 + 引用对其他线程可见

实现方式:
1. final 域(构造函数内初始化)
2. volatile 变量
3. synchronized 保护
4. static 静态初始化
5. 使用并发容器(ConcurrentHashMap 等)

Q10:as-if-serial 和 happens-before 的区别是什么?

参考答案

as-if-serial(单线程语义)

定义:无论如何重排序,单线程程序的执行结果不能改变。

作用范围:单线程

示例

int a = 1;  // ①
int b = 2;  // ②
int c = a + b;  // ③

// as-if-serial 允许:
// ② ①  ③(交换 ① 和 ②,因为无依赖)
// ① ② ③(保持顺序)

// as-if-serial 禁止:
// ③ ① ②(③ 依赖 ① 和 ②,结果会改变)

关键点

  • 编译器和处理器遵循数据依赖性
  • 只保证单线程语义正确
  • 多线程下可能出现问题

happens-before(多线程语义)

定义:定义操作的偏序关系,保证多线程下的内存可见性。

作用范围:多线程

示例

// 线程 A
x = 1;          // ①
flag = true;    // ② volatile 写

// 线程 B
if (flag) {     // ③ volatile 读
    int y = x;  // ④
}

// happens-before 规则保证:
// ① happens-before ②(程序顺序规则)
// ② happens-before ③(volatile 规则)
// ③ happens-before ④(程序顺序规则)
// 结论:① happens-before ④(y 一定读到 1)

对比表

对比项 as-if-serial happens-before
适用范围 单线程 多线程
保证内容 执行结果不变 内存可见性
重排序限制 只考虑数据依赖 考虑同步关系
典型场景 编译器优化 volatile、锁、线程操作

关系

as-if-serial 是 happens-before 的基础(程序顺序规则)

happens-beforeas-if-serial 在多线程下的扩展

九、总结与最佳实践

9.1 内存模型核心要点

JMM 三大特性:
┌─────────────────────────────────────┐
│ 1. 原子性(Atomicity)              │
│    - synchronized                    │
│    - Lock                            │
│    - Atomic 类                       │
├─────────────────────────────────────┤
│ 2. 可见性(Visibility)             │
│    - volatile                        │
│    - synchronized                    │
│    - final                           │
├─────────────────────────────────────┤
│ 3. 有序性(Ordering)               │
│    - happens-before 规则             │
│    - 内存屏障                        │
│    - volatile / synchronized         │
└─────────────────────────────────────┘

9.2 并发编程最佳实践

原则 1:优先使用不可变对象

// ✅ 推荐
public final class ImmutablePoint {
    private final int x;
    private final int y;
    public ImmutablePoint(int x, int y) {
        this.x = x;
        this.y = y;
    }
    
    // 无需同步,天然线程安全
    public int getX() { return x; }
    public int getY() { return y; }
    
    // 修改时返回新对象
    public ImmutablePoint move(int dx, int dy) {
        return new ImmutablePoint(x + dx, y + dy);
    }
}

// 优势:
// 1. 无需同步,性能最高
// 2. 可以安全地在多线程间共享
// 3. 可以作为 HashMap 的 key

原则 2:最小化同步范围

// ❌ 不推荐:同步整个方法
public synchronized void process(List<Task> tasks) {
    List<Task> validTasks = validate(tasks);  // 耗时操作
    for (Task task : validTasks) {
        execute(task);  // 耗时操作
    }
    updateStatus();  // 只有这一步需要同步
}

// ✅ 推荐:只同步必要的部分
public void process(List<Task> tasks) {
    List<Task> validTasks = validate(tasks);  // 不需要锁
    for (Task task : validTasks) {
        execute(task);  // 不需要锁
    }
    synchronized (this) {
        updateStatus();  // 只锁定关键区域
    }
}

原则 3:优先使用并发工具类

// ❌ 不推荐:手动同步
public class ManualSync {
    private final Map<String, User> cache = new HashMap<>();
    
    public synchronized void put(String key, User user) {
        cache.put(key, user);
    }
    
    public synchronized User get(String key) {
        return cache.get(key);
    }
}

// ✅ 推荐:使用 ConcurrentHashMap
public class ConcurrentSync {
    private final Map<String, User> cache = new ConcurrentHashMap<>();
    
    public void put(String key, User user) {
        cache.put(key, user);  // 内部使用分段锁,性能更高
    }
    
    public User get(String key) {
        return cache.get(key);  // 读操作无锁
    }
}

原则 4:避免在锁内调用外部方法

// ❌ 危险:锁内调用外部方法(可能死锁)
public class DangerousLock {
    private final Object lock = new Object();
    
    public void process(Callback callback) {
        synchronized (lock) {
            // ...
            callback.onComplete();  // ⚠️ 外部方法可能再次获取锁
        }
    }
}

// ✅ 安全:锁外调用外部方法
public class SafeLock {
    private final Object lock = new Object();
    
    public void process(Callback callback) {
        Object result;
        synchronized (lock) {
            result = doWork();  // 锁内只处理必要操作
        }
        callback.onComplete(result);  // 锁外调用回调
    }
}

原则 5:正确使用 volatile

// ✅ 适合使用 volatile 的场景

// 场景 1:状态标志
public class Server {
    private volatile boolean shutdown = false;
    
    public void shutdown() {
        shutdown = true;  // 单次赋值
    }
    
    public void run() {
        while (!shutdown) {  // 读取最新值
            processRequest();
        }
    }
}

// 场景 2:安全发布
public class ResourceManager {
    private volatile Resource resource;
    
    public void init() {
        Resource temp = new Resource();
        temp.load();  // 完全初始化
        resource = temp;  // volatile 写,安全发布
    }
}

// 场景 3:独立观察
public class Monitor {
    private volatile long lastUpdateTime;
    
    public void recordUpdate() {
        lastUpdateTime = System.currentTimeMillis();  // 单次写入
    }
    
    public boolean isStale() {
        return System.currentTimeMillis() - lastUpdateTime > TIMEOUT;
    }
}

// ❌ 不适合使用 volatile 的场景

// 场景 1:复合操作
private volatile int count = 0;

public void increment() {
    count++;  // ❌ 非原子操作,volatile 无法保证
}

// 正确做法:
private AtomicInteger count = new AtomicInteger(0);

public void increment() {
    count.incrementAndGet();  // ✅ CAS 原子操作
}

// 场景 2:多变量一致性
private volatile int x;
private volatile int y;

public void update(int newX, int newY) {
    x = newX;  // ❌ 两个变量的更新不是原子的
    y = newY;  // ⚠️ 读线程可能看到不一致的状态
}

// 正确做法:
private int x;
private int y;

public synchronized void update(int newX, int newY) {
    x = newX;  // ✅ 锁保证原子性和可见性
    y = newY;
}

9.3 常见并发错误及避免方法

错误 1:双重检查锁忘记 volatile

// ❌ 错误
public class Singleton {
    private static Singleton instance;
    
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();  // ⚠️ 重排序问题
                }
            }
        }
        return instance;
    }
}

// ✅ 正确
public class Singleton {
    private static volatile Singleton instance;  // 添加 volatile
    
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

错误 2:误用 volatile 数组

// ❌ 错误
public class VolatileArrayError {
    private volatile int[] arr = new int[10];
    
    public void update(int index, int value) {
        arr[index] = value;  // ⚠️ volatile 不保护数组元素
    }
}

// ✅ 正确方案 1:使用 AtomicIntegerArray
public class AtomicArrayCorrect {
    private AtomicIntegerArray arr = new AtomicIntegerArray(10);
    
    public void update(int index, int value) {
        arr.set(index, value);  // ✅ 原子操作
    }
}

// ✅ 正确方案 2:使用同步
public class SyncArrayCorrect {
    private int[] arr = new int[10];
    
    public synchronized void update(int index, int value) {
        arr[index] = value;  // ✅ 锁保护
    }
}

错误 3:读写不对称的同步

// ❌ 错误:写加锁,读不加锁
public class AsymmetricSync {
    private int value;
    
    public synchronized void setValue(int value) {
        this.value = value;  // ✅ 写加锁
    }
    
    public int getValue() {
        return value;  // ❌ 读不加锁,可能读到旧值
    }
}

// ✅ 正确方案 1:读写都加锁
public class SymmetricSync {
    private int value;
    
    public synchronized void setValue(int value) {
        this.value = value;
    }
    
    public synchronized int getValue() {
        return value;  // ✅ 读也加锁
    }
}

// ✅ 正确方案 2:使用 volatile
public class VolatileSync {
    private volatile int value;  // volatile 保证可见性
    
    public void setValue(int value) {
        this.value = value;  // 无需加锁
    }
    
    public int getValue() {
        return value;  // 无需加锁
    }
}

错误 4:final 变量引用可变对象

// ⚠️ 潜在问题
public class FinalMutable {
    private final List<String> list = new ArrayList<>();
    
    public FinalMutable() {
        list.add("A");  // ✅ 构造函数内,final 保护
    }
    
    public void addElement(String s) {
        list.add(s);  // ⚠️ 构造函数外修改,final 不保护
    }
    
    // 其他线程可能读到不一致的 list 状态
}

// ✅ 正确:使用不可变集合
public class FinalImmutable {
    private final List<String> list;
    
    public FinalImmutable() {
        List<String> temp = new ArrayList<>();
        temp.add("A");
        list = Collections.unmodifiableList(temp);  // 不可变
    }
    
    // list 完全线程安全
}

9.4 性能优化技巧

技巧 1:减少锁粒度

// ❌ 粗粒度锁
public class CoarseLock {
    private final Map<String, User> cache = new HashMap<>();
    
    public synchronized void put(String key, User user) {
        cache.put(key, user);  // 整个 Map 加锁
    }
}

// ✅ 细粒度锁(使用 ConcurrentHashMap)
public class FineLock {
    private final Map<String, User> cache = new ConcurrentHashMap<>();
    
    public void put(String key, User user) {
        cache.put(key, user);  // 只锁定对应的分段
    }
}

// 性能对比:
// 单线程:相差不大
// 多线程:ConcurrentHashMap 提升 3-5 倍

技巧 2:读写分离

// ✅ 使用 ReadWriteLock
public class ReadWriteCache {
    private final Map<String, User> cache = new HashMap<>();
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    
    public User get(String key) {
        lock.readLock().lock();  // 读锁(允许多个线程)
        try {
            return cache.get(key);
        } finally {
            lock.readLock().unlock();
        }
    }
    
    public void put(String key, User user) {
        lock.writeLock().lock();  // 写锁(独占)
        try {
            cache.put(key, user);
        } finally {
            lock.writeLock().unlock();
        }
    }
}

// 适用场景:读多写少(读写比 > 10:1)

技巧 3:使用局部变量

// ❌ 共享变量(需要同步)
public class SharedVariable {
    private StringBuilder buffer = new StringBuilder();
    
    public synchronized String process(List<String> data) {
        buffer.setLength(0);
        for (String s : data) {
            buffer.append(s);
        }
        return buffer.toString();
    }
}

// ✅ 局部变量(无需同步)
public class LocalVariable {
    public String process(List<String> data) {
        StringBuilder buffer = new StringBuilder();  // 线程私有
        for (String s : data) {
            buffer.append(s);
        }
        return buffer.toString();
    }
}

// 性能提升:10 倍以上(无锁竞争)

技巧 4:延迟初始化

// ✅ Holder 模式(推荐)
public class LazyInit {
    private static class Holder {
        static final Resource resource = new Resource();  // 类加载时初始化
    }
    
    public static Resource getResource() {
        return Holder.resource;  // 懒加载 + 线程安全 + 高性能
    }
}

// 优势:
// 1. 第一次调用时才初始化
// 2. JVM 保证线程安全
// 3. 无锁,性能极高

9.5 并发工具选择指南

┌─────────────────────────────────────────────────────┐
│           并发场景                工具选择            │
├─────────────────────────────────────────────────────┤
│ 状态标志                        volatile boolean     │
│ 计数器(低并发)                AtomicInteger        │
│ 计数器(高并发)                LongAdder            │
│ 单例模式                        静态内部类/枚举      │
│ 缓存                            ConcurrentHashMap    │
│ 队列                            ConcurrentLinkedQueue│
│ 阻塞队列                        BlockingQueue        │
│ 读多写少                        ReadWriteLock        │
│ 复杂同步                        Semaphore/CountDown  │
│ 线程池                          ThreadPoolExecutor   │
└─────────────────────────────────────────────────────┘

⬅️ 垃圾回收与GC算法 🏠 00-Java ➡️ java的优势