Java内存模型(JMM)
Java内存模型(Java Memory Model, JMM)是Java并发编程的核心基础,它定义了多线程环境下变量的访问规则,决定了在什么时候一个线程对共享变量的修改对其他线程可见。本文将全面剖析JMM的各个方面,从基础概念到高级应用,帮助开发者彻底理解并正确运用这一关键机制。
一、JMM概述:为什么需要内存模型?
1.1 计算机体系结构的内存问题
现代计算机体系结构中存在多个层次的内存访问优化,这导致了内存访问的一些特殊现象:
CPU缓存架构:多级缓存(L1/L2/L3)的存在导致内存可见性问题
指令重排序:编译器/处理器为了优化性能可能改变指令执行顺序
多核并发:不同CPU核心可能看到不同的内存状态
// 典型的内存可见性问题示例
public class VisibilityProblem {private static boolean ready = false;private static int number = 0;public static void main(String[] args) {new Thread(() -> {while (!ready) {// 可能永远循环!}System.out.println(number);}).start();number = 42;ready = true;}
}
1.2 JMM的作用与意义
Java内存模型的主要目标:
定义规则:规定线程如何与内存交互
保证可见性:确保一个线程的修改对其他线程可见
禁止重排序:限制编译器和处理器的优化行为
提供同步机制:定义happens-before关系
二、JMM核心概念详解
2.1 主内存与工作内存
JMM将内存抽象为两种:
主内存(Main Memory):所有共享变量的存储位置
工作内存(Working Memory):每个线程私有的内存空间
变量访问流程:
线程从主内存拷贝变量到工作内存
线程在工作内存中操作变量
在某个时刻将工作内存的值刷新回主内存
2.2 内存间交互操作
JMM定义了8种原子操作来完成主内存与工作内存的交互:
操作 | 作用 |
---|---|
lock(锁定) | 作用于主内存,标识变量为线程独占 |
unlock(解锁) | 释放锁定状态 |
read(读取) | 从主内存传输变量到工作内存 |
load(载入) | 把read得到的值放入工作内存副本 |
use(使用) | 把工作内存值传递给执行引擎 |
assign(赋值) | 将执行引擎接收的值赋给工作内存变量 |
store(存储) | 把工作内存值传送到主内存 |
write(写入) | 把store得到的值放入主内存变量 |
操作规则:
read/load和store/write必须成对出现
不允许一个变量从主内存读取但工作内存不接受,或工作内存发起回写但主内存不接受
新变量只能在主内存"诞生"
一个变量同一时刻只允许一个线程lock
对变量执行lock操作会清空工作内存中此变量的值
unlock前必须先把变量同步回主内存
2.3 happens-before原则
happens-before是JMM的核心概念,定义了两个操作的偏序关系:
基本规则:
程序顺序规则:同一线程中的每个操作happens-before于该线程中的任意后续操作
监视器锁规则:对一个锁的解锁happens-before于随后对这个锁的加锁
volatile变量规则:对一个volatile域的写happens-before于任意后续对这个volatile域的读
传递性:如果A happens-before B,且B happens-before C,那么A happens-before C
线程启动规则:Thread.start()的调用happens-before于被启动线程中的任何操作
线程终止规则:线程中的任何操作都happens-before于其他线程检测到该线程已经终止
中断规则:一个线程调用另一个线程的interrupt happens-before于被中断线程检测到中断
终结器规则:对象的构造函数执行happens-before于它的finalize方法
// happens-before示例
public class HappensBeforeExample {private int x = 0;private volatile boolean v = false;public void writer() {x = 42; // (1)v = true; // (2) volatile写}public void reader() {if (v) { // (3) volatile读System.out.println(x); // (4) 保证输出42}}
}
三、volatile关键字深度解析
3.1 volatile的语义
volatile变量具有两种特性:
可见性:对volatile变量的写操作会立即刷新到主内存
禁止重排序:编译器/处理器不会对volatile操作重排序
内存屏障:
写volatile变量时:在写操作前插入StoreStore屏障,写操作后插入StoreLoad屏障
读volatile变量时:在读操作前插入LoadLoad屏障,读操作后插入LoadStore屏障
3.2 volatile与普通变量的区别
特性 | 普通变量 | volatile变量 |
---|---|---|
可见性 | 不保证 | 保证 |
原子性 | 32位以下基本类型具有原子性 | 任何读写操作都具有原子性 |
重排序 | 允许 | 限制 |
性能 | 高 | 较低(因为内存屏障) |
3.3 volatile的正确使用场景
状态标志:
public class ShutdownRequest extends Thread {private volatile boolean shutdownRequested = false;public void shutdown() { shutdownRequested = true; }@Overridepublic void run() {while (!shutdownRequested) {// 执行任务}} }
一次性安全发布:
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;} }
独立观察:
public class UserManager {private volatile String lastUser;public void authenticate(String user, String password) {boolean valid = // 验证逻辑if (valid) {lastUser = user;}}
}
四、synchronized与锁的内存语义
4.1 synchronized的三大特性
原子性:确保互斥执行临界区代码
可见性:解锁前必须将变量刷新到主内存
有序性:限制临界区内指令重排序
4.2 锁的内存语义
获取锁:清空工作内存,从主内存重新加载变量
释放锁:将工作内存中的变量刷新到主内存
public class SynchronizedExample {private int value = 0;public synchronized void increment() {value++; // 原子性+可见性保证}public int getValue() {synchronized(this) {return value; // 保证读取最新值}}
}
4.3 锁与volatile的比较
特性 | synchronized | volatile |
---|---|---|
原子性 | 保证代码块/方法级别的原子性 | 保证单个读/写的原子性 |
可见性 | 保证 | 保证 |
阻塞 | 是 | 否 |
适用范围 | 代码块/方法 | 变量 |
编译器优化 | 限制 | 限制 |
性能 | 较高开销 | 较低开销 |
五、final域的内存语义
5.1 final域的重排序规则
写final域:禁止把final域的写重排序到构造函数之外
读final域:初次读包含final域的对象引用时,保证final域已被初始化
5.2 final的正确使用
public class FinalExample {private final int x;private int y;private static FinalExample instance;public FinalExample() {x = 1; // final写y = 2; // 普通写}public static void writer() {instance = new FinalExample();}public static void reader() {FinalExample object = instance;int a = object.x; // 保证读到1int b = object.y; // 可能读到0}
}
5.3 final与线程安全
正确构造的不可变对象是线程安全的:
public class ImmutablePoint {private final int x;private final int y;public ImmutablePoint(int x, int y) {this.x = x;this.y = y;}// 只有getter方法
}
六、双重检查锁定问题与解决方案
6.1 错误的双重检查锁定
public class DoubleCheckedLocking {private static Instance instance;public static Instance getInstance() {if (instance == null) { // 第一次检查synchronized (DoubleCheckedLocking.class) {if (instance == null) { // 第二次检查instance = new Instance(); // 问题根源!}}}return instance;}
}
问题根源:instance = new Instance()
可能被重排序为:
分配内存空间
将引用指向内存空间(此时instance!=null)
初始化对象
6.2 正确的解决方案
使用volatile:
private volatile static Instance instance;
静态内部类:
public class Singleton {private static class Holder {static final Instance INSTANCE = new Instance();}public static Instance getInstance() {return Holder.INSTANCE;} }
枚举实现:
public enum Singleton {INSTANCE;public void someMethod() { ... } }
七、JMM与并发工具类
7.1 Atomic类的内存语义
原子类基于CAS实现,具有volatile读写的内存语义:
public class AtomicExample {private final AtomicInteger counter = new AtomicInteger(0);public void increment() {counter.incrementAndGet(); // 包含内存屏障}public int get() {return counter.get(); // 具有volatile读语义}
}
7.2 ConcurrentHashMap的内存保证
ConcurrentHashMap通过分段锁和volatile变量保证内存可见性:
static final class Node<K,V> implements Map.Entry<K,V> {final int hash;final K key;volatile V val; // 值用volatile修饰volatile Node<K,V> next; // 下一个节点也用volatile修饰
}
7.3 CountDownLatch的内存语义
public class CountDownLatchDemo {private final CountDownLatch latch = new CountDownLatch(1);private int result;public void compute() {new Thread(() -> {result = doCompute(); // (1)latch.countDown(); // (2) 释放存储屏障}).start();}public int getResult() throws InterruptedException {latch.await(); // (3) 获取加载屏障return result; // (4) 保证看到(1)的结果}
}
八、JMM实战问题与解决方案
8.1 伪共享(False Sharing)问题
问题现象:
多个线程频繁修改同一缓存行的不同变量
导致缓存行无效,性能下降
解决方案:
填充(Padding):
public class FalseSharing {public volatile long value1;public long p1, p2, p3, p4, p5, p6, p7; // 填充public volatile long value2; }
使用@Contended注解(Java8+):
public class FalseSharing {@jdk.internal.vm.annotation.Contendedpublic volatile long value1;@jdk.internal.vm.annotation.Contendedpublic volatile long value2; }
8.2 安全发布模式
静态初始化:
public static Holder holder = new Holder(42);
volatile或AtomicReference:
public volatile Holder holder;
final域:
public class HolderWrapper {public final Holder holder;public HolderWrapper(Holder holder) {this.holder = holder;} }
九、JMM与Java新特性
9.1 Java 9+的VarHandle
提供更灵活的内存访问操作:
public class VarHandleExample {private int x;private static final VarHandle X;static {try {X = MethodHandles.lookup().findVarHandle(VarHandleExample.class, "x", int.class);} catch (Exception e) {throw new Error(e);}}public void increment() {X.getAndAdd(this, 1); // 原子操作}
}
十、JMM最佳实践
优先使用高层并发工具:
使用ConcurrentHashMap而不是同步的HashMap
使用AtomicLong而不是synchronized计数器
最小化同步范围:
同步块比同步方法更好
只在必要时使用同步
正确发布共享对象:
使用final字段
使用volatile或正确同步
避免过度同步:
考虑使用不可变对象
使用线程封闭技术(ThreadLocal)
理解happens-before关系:
不要依赖执行时序
确保正确的同步
性能考虑:
测量而不是猜测
注意伪共享问题
考虑无锁算法