--- title: "04-JMM与内存可见性" created: 2025-11-27 tags: - Java --- # 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 的值写入主内存 | **示例流程**: ```java // 线程 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 经典案例:循环不停止** ```java 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(false) t3: 线程 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)**: ```java 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:读取到部分更新的值** ```java 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:编译器重排序** ```java // 原始代码 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 指令重排序** ```java // 原始代码 a = 1; // 写操作 1 b = 2; // 写操作 2 // CPU 可能重排为(写缓冲优化): b = 2; // 先写 b(缓冲区有空闲) a = 1; // 后写 a ``` --- ### **3.3 重排序的经典问题:双重检查锁(DCL)** ```java 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; } } ``` 问题分析: ```java // 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; ⚠️ 半初始化对象 初始化对象(太晚了) ``` 正确写法: ```java 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 语义** **定义**:不管怎么重排序,单线程程序的执行结果不能改变。 ```java // 示例: 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)** **定义**:单线程内,按照代码顺序执行。 ```java 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) ```java int a = 1; int b = 2; // 可能与 a 重排序(无依赖) int c = a + b; // 但 c 一定在 a, b 之后 ``` --- #### **规则 2:volatile 变量规则(Volatile Variable Rule)** **定义**:对 volatile 变量的写操作 happens-before 后续对该变量的读操作。 ```java 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 后续对该锁的加锁。 ```java 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 该线程的任何操作。 ```java 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() 返回)。 ```java 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 被中断线程检测到中断事件。 ```java 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() 方法的开始。 ```java 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。 ```java 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 不等于时间先后顺序! ```java int a = 1; // ① int b = 2; // ② // ① happens-before ②(程序顺序规则) // 但实际执行可能:② 先执行,① 后执行(重排序) // 只要保证:如果 ② 依赖 ①,则 ① 的结果对 ② 可见 ``` **示例:允许重排序的情况** ```java 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 的两大特性** | **特性** | **说明** | **示例** | | --- | --- | --- | | **可见性** | 写操作立即刷新到主内存,读操作从主内存读取 | 状态标志 | | **有序性** | 禁止指令重排序(内存屏障) | 双重检查锁 | ⚠️ **不保证原子性**: ```java private volatile int count = 0; count++; // ❌ 非原子操作(读-改-写) // 等价于: // int temp = count; // 读 // temp = temp + 1; // 改 // count = temp; // 写 // 多线程下会丢失更新 ``` --- ### **5.2 volatile 的内存语义** #### **写操作的内存语义** ```java 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:状态标志** ```java public class StatusFlag { private volatile boolean shutdown = false; public void shutdown() { shutdown = true; // 写线程 } public void doWork() { while (!shutdown) { // 读线程 // 执行任务 } } } ``` #### **✅ 场景 2:双重检查锁(DCL)** ```java 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:独立观察(一写多读)** ```java public class UserManager { private volatile User currentUser; // 写线程(单线程) public void updateUser(User user) { currentUser = user; } // 读线程(多线程) public User getCurrentUser() { return currentUser; } } ``` #### **❌ 场景 4:计数器(需要原子性)** ```java 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 域的写操作重排序到构造函数之外。 ```java 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; // ⑦ 读取普通域 } } ``` **重排序分析**: ```java // ✅ 允许的重排序(普通域): obj = new FinalExample(); // 先赋值引用 y = 2; // 后初始化 y(⚠️ 可能导致读到 0) // ❌ 禁止的重排序(final 域): obj = new FinalExample(); // 先赋值引用 x = 1; // 后初始化 x(禁止!) // 保证:如果读线程看到 obj != null,则 x 一定初始化完成 ``` **内存屏障实现**(JVM 层面): ```java 构造函数: x = 1; // final 域写入 y = 2; // 普通域写入 StoreStore 屏障 ← 插入内存屏障 } // 构造函数结束 obj = new FinalExample(); // 对象引用赋值 ``` --- #### **规则 2:final 域的读取重排序规则** **定义**:禁止初次读取对象引用与读取 final 域重排序。 ```java 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 域引用的对象** ```java 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 ✅ } } ``` **额外规则**: ```java 构造函数内对 final 引用对象的成员域的写入: arr = new int[10]; arr[0] = 1; ← 这个写入也受 final 保护 StoreStore 屏障 } // 构造函数结束 // 保证:读线程看到 obj != null 时,arr[0] 一定初始化完成 ``` ⚠️ **注意**:final 只保护构造函数内的初始化,不保护后续修改! ```java public class FinalMutableExample { private final List list = new ArrayList<>(); public FinalMutableExample() { list.add("A"); // ✅ 构造函数内,受保护 } public void addElement(String s) { list.add(s); // ❌ 构造函数外,不受 final 保护 // 需要额外的同步机制 } } ``` --- ### **6.4 final 的实际应用场景** #### **应用 1:安全发布不可变对象** ```java // 不使用 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 类的不可变性** ```java 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)** ```java 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:懒加载(线程不安全)** ```java public class Singleton { private static Singleton instance; public static Singleton getInstance() { if (instance == null) { // ❌ 多线程下会创建多个实例 instance = new Singleton(); } return instance; } } ``` **问题**: ```java 线程 A 线程 B ────────────────────────────────────────── if (instance == null) ✓ if (instance == null) ✓ instance = new Singleton(); instance = new Singleton(); // 创建了两个实例 ❌ ``` --- #### **版本 2:加锁(性能差)** ```java public class Singleton { private static Singleton instance; public static synchronized Singleton getInstance() { // ✅ 线程安全 if (instance == null) { instance = new Singleton(); } return instance; } } ``` **问题**:每次调用都要加锁,性能差(即使已经初始化)。 --- #### **版本 3:双重检查锁(DCL,有问题)** ```java 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; } } ``` **问题分析**(重排序导致): ```java // 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(正确)✅** ```java 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 的作用**: ```java // volatile 写操作会插入 StoreStore 屏障: memory = allocate(); ctorInstance(memory); StoreStore 屏障 ← 禁止上面的初始化与下面的引用赋值重排序 instance = memory; // 保证:读线程看到 instance != null 时,对象一定初始化完成 ``` --- #### **版本 5:静态内部类(推荐)✅** ```java 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:枚举(最佳实践)✅** ```java public enum Singleton { INSTANCE; public void doSomething() { // 业务方法 } } // 使用: Singleton.INSTANCE.doSomething(); ``` **优势**: - 天然单例(JVM 保证) - 线程安全 - 防止反序列化破坏单例 - 防止反射攻击 --- ### **案例 2:生产者-消费者模式(volatile 实现)** ```java 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:配置类的安全发布** ```java // 不安全的发布 public class UnsafeConfig { private Map 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 config; // ✅ final 保证可见性 public SafeConfig() { Map 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)** ```java 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 数组问题(常见误区)** ```java // ❌ 错误示例 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** | 不可变对象 | 极高 | **示例**: ```java // 问题代码 private static boolean flag = false; // 解决方案 private static volatile boolean flag = false; // ✅ ``` --- ### **Q3:volatile 有什么作用?能保证原子性吗?** **参考答案**: **作用**(两大特性): 1. **可见性**: - 写操作:立即刷新到主内存 - 读操作:从主内存读取最新值 2. **有序性**: - 禁止指令重排序(通过内存屏障) - 保证 volatile 变量前后的操作不会被重排序 **不保证原子性**: ```java 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**:单线程内结果不变 - **数据依赖性**:有依赖的操作不会重排序 **示例**: ```java int a = 1; // ① int b = 2; // ② int c = a + b; // ③ // 允许重排序:① 和 ② 交换(无依赖) // 禁止重排序:③ 不能移到 ① 或 ② 之前(有依赖) ``` **多线程问题**: ```java // 线程 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 | 传递链 | **示例**: ```java 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?** **参考答案**: **问题代码**: ```java 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; } } ``` **问题分析**: ```java // 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 的作用: ```java private static volatile Singleton instance; // ✅ 添加 volatile // volatile 写操作插入 StoreStore 屏障: memory = allocate(); ctorInstance(memory); StoreStore 屏障 ← 禁止重排序 instance = memory; // 保证:读线程看到 instance != null 时,对象一定初始化完成 ``` **时序图**: ```java 无 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** | | --- | --- | --- | | **可见性** | ✅ 保证 | ✅ 保证 | | **原子性** | ❌ 不保证 | ✅ 保证 | | **有序性** | ✅ 禁止重排序 | ✅ 锁机制保证 | | **性能** | 高(无锁,轻量级) | 低(加锁,重量级) | | **阻塞** | 不阻塞 | 阻塞 | | **语义** | 变量级别 | 代码块/方法级别 | | **锁升级** | 无 | 有(偏向锁→轻量级锁→重量级锁) | **使用场景**: ```java // 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 域前,一定先读取对象引用 实现机制: ```java 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 引用类型的特殊保护**: ```java public class FinalReferenceExample { private final int[] arr; public FinalReferenceExample() { arr = new int[10]; arr[0] = 1; // ✅ 这个写入也受 final 保护 // StoreStore 屏障 } // 保证:读线程看到 arr != null 时,arr[0] 一定等于 1 } ``` **典型应用**: ```java // 不可变对象(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:如何安全地发布对象?** **参考答案**: **不安全发布示例**: ```java public class UnsafePublish { private Map config; public UnsafePublish() { config = new HashMap<>(); config.put("key", "value"); // ⚠️ 另一个线程可能读到: // 1. config = null(未初始化) // 2. config != null,但 Map 为空(部分初始化) } } ``` **安全发布的四种方式**: #### **方式 1:使用 final 关键字** ```java public class SafePublish1 { private final Map config; // ✅ final 保证可见性 public SafePublish1() { Map temp = new HashMap<>(); temp.put("key", "value"); config = Collections.unmodifiableMap(temp); // 不可变 } } ``` #### **方式 2:使用 volatile 关键字** ```java public class SafePublish2 { private volatile Map config; // ✅ volatile 保证可见性 public void init() { Map temp = new HashMap<>(); temp.put("key", "value"); config = temp; // volatile 写 } } ``` #### **方式 3:使用 synchronized 同步** ```java public class SafePublish3 { private Map config; public synchronized void init() { // ✅ 锁保证可见性 config = new HashMap<>(); config.put("key", "value"); } public synchronized Map getConfig() { return config; } } ``` #### **方式 4:使用静态初始化器** ```java public class SafePublish4 { // ✅ 类加载时初始化,JVM 保证线程安全 private static final Map 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(单线程语义)** **定义**:无论如何重排序,单线程程序的执行结果不能改变。 **作用范围**:单线程 **示例**: ```java int a = 1; // ① int b = 2; // ② int c = a + b; // ③ // as-if-serial 允许: // ② ① ③(交换 ① 和 ②,因为无依赖) // ① ② ③(保持顺序) // as-if-serial 禁止: // ③ ① ②(③ 依赖 ① 和 ②,结果会改变) ``` **关键点**: - 编译器和处理器遵循数据依赖性 - 只保证单线程语义正确 - 多线程下可能出现问题 #### **happens-before(多线程语义)** **定义**:定义操作的偏序关系,保证多线程下的内存可见性。 **作用范围**:多线程 **示例**: ```java // 线程 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-before 是 as-if-serial 在多线程下的扩展 ``` ## **九、总结与最佳实践** ### **9.1 内存模型核心要点** ``` JMM 三大特性: ┌─────────────────────────────────────┐ │ 1. 原子性(Atomicity) │ │ - synchronized │ │ - Lock │ │ - Atomic 类 │ ├─────────────────────────────────────┤ │ 2. 可见性(Visibility) │ │ - volatile │ │ - synchronized │ │ - final │ ├─────────────────────────────────────┤ │ 3. 有序性(Ordering) │ │ - happens-before 规则 │ │ - 内存屏障 │ │ - volatile / synchronized │ └─────────────────────────────────────┘ ``` ### **9.2 并发编程最佳实践** #### **原则 1:优先使用不可变对象** ```java // ✅ 推荐 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:最小化同步范围** ```java // ❌ 不推荐:同步整个方法 public synchronized void process(List tasks) { List validTasks = validate(tasks); // 耗时操作 for (Task task : validTasks) { execute(task); // 耗时操作 } updateStatus(); // 只有这一步需要同步 } // ✅ 推荐:只同步必要的部分 public void process(List tasks) { List validTasks = validate(tasks); // 不需要锁 for (Task task : validTasks) { execute(task); // 不需要锁 } synchronized (this) { updateStatus(); // 只锁定关键区域 } } ``` #### **原则 3:优先使用并发工具类** ```java // ❌ 不推荐:手动同步 public class ManualSync { private final Map 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 cache = new ConcurrentHashMap<>(); public void put(String key, User user) { cache.put(key, user); // 内部使用分段锁,性能更高 } public User get(String key) { return cache.get(key); // 读操作无锁 } } ``` #### **原则 4:避免在锁内调用外部方法** ```java // ❌ 危险:锁内调用外部方法(可能死锁) 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** ```java // ✅ 适合使用 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** ```java // ❌ 错误 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 数组** ```java // ❌ 错误 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:读写不对称的同步** ```java // ❌ 错误:写加锁,读不加锁 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 变量引用可变对象** ```java // ⚠️ 潜在问题 public class FinalMutable { private final List 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 list; public FinalImmutable() { List temp = new ArrayList<>(); temp.add("A"); list = Collections.unmodifiableList(temp); // 不可变 } // list 完全线程安全 } ``` ### **9.4 性能优化技巧** #### **技巧 1:减少锁粒度** ```java // ❌ 粗粒度锁 public class CoarseLock { private final Map cache = new HashMap<>(); public synchronized void put(String key, User user) { cache.put(key, user); // 整个 Map 加锁 } } // ✅ 细粒度锁(使用 ConcurrentHashMap) public class FineLock { private final Map cache = new ConcurrentHashMap<>(); public void put(String key, User user) { cache.put(key, user); // 只锁定对应的分段 } } // 性能对比: // 单线程:相差不大 // 多线程:ConcurrentHashMap 提升 3-5 倍 ``` #### **技巧 2:读写分离** ```java // ✅ 使用 ReadWriteLock public class ReadWriteCache { private final Map 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:使用局部变量** ```java // ❌ 共享变量(需要同步) public class SharedVariable { private StringBuilder buffer = new StringBuilder(); public synchronized String process(List data) { buffer.setLength(0); for (String s : data) { buffer.append(s); } return buffer.toString(); } } // ✅ 局部变量(无需同步) public class LocalVariable { public String process(List data) { StringBuilder buffer = new StringBuilder(); // 线程私有 for (String s : data) { buffer.append(s); } return buffer.toString(); } } // 性能提升:10 倍以上(无锁竞争) ``` #### **技巧 4:延迟初始化** ```java // ✅ 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 │ └─────────────────────────────────────────────────────┘ ``` --- ⬅️ [[03-垃圾回收与GC算法|垃圾回收与GC算法]] 🏠 [[00-Java|00-Java]] ➡️ [[00-java的优势|java的优势]]