Java内存模型(JMM)与多线程并发机制详解
Java内存模型(JMM)与多线程并发机制深度解析
一、Java内存模型(JMM)基础
1.1 JMM核心概念
Java Memory Model(JMM)定义了Java程序中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取变量的底层细节。
关键特性:
- 所有变量都存储在主内存中
- 每个线程有自己的工作内存(缓存)
- 线程对变量的所有操作都必须在工作内存中进行
- 不同线程之间无法直接访问对方工作内存中的变量
1.2 主内存与工作内存交互
JMM定义了8种原子操作来完成主内存与工作内存的交互:
- lock(锁定):作用于主内存变量
- unlock(解锁):作用于主内存变量
- read(读取):从主内存传输到工作内存
- load(载入):将read得到的值放入工作内存变量副本
- use(使用):将工作内存变量值传递给执行引擎
- assign(赋值):将执行引擎接收的值赋给工作内存变量
- store(存储):将工作内存变量值传送到主内存
- write(写入):将store得到的值放入主内存变量
实践建议:
- 理解这些原子操作有助于分析多线程环境下的可见性问题
- 在编写高并发程序时,要特别注意这些操作的顺序和组合
二、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于对此线程的终止检测
示例代码:
// 示例1:volatile的happens-before关系
class VolatileExample {
int x = 0;
volatile boolean v = false;
public void writer() {
x = 42; // 1
v = true; // 2
}
public void reader() {
if (v) { // 3
System.out.println(x); // 4
}
}
}
实践建议:
- 合理利用happens-before原则可以减少同步开销
- 在代码注释中明确happens-before关系,便于维护
三、内存屏障
内存屏障(Memory Barrier)是CPU或编译器对内存操作顺序的约束,用于保证特定操作的内存可见性。
主要类型:
- LoadLoad屏障:序列:Load1; LoadLoad; Load2
- StoreStore屏障:序列:Store1; StoreStore; Store2
- LoadStore屏障:序列:Load1; LoadStore; Store2
- StoreLoad屏障:序列:Store1; StoreLoad; Load2
JVM中的实现:
// 伪代码展示内存屏障使用
public class MemoryBarrierExample {
private int value;
private volatile boolean flag;
public void setValue(int newValue) {
value = newValue; // 普通写
// StoreStore屏障
flag = true; // volatile写
}
public int getValue() {
if (flag) { // volatile读
// LoadLoad屏障 + LoadStore屏障
return value; // 普通读
}
return -1;
}
}
实践建议:
- 理解内存屏障有助于优化高性能并发代码
- 大多数情况下应使用高级同步机制而非直接操作内存屏障
四、线程实现与调度
4.1 线程状态转换
4.2 协程(Loom项目)
Java 19引入的虚拟线程(协程)特性:
与传统线程对比:
- 轻量级:一个JVM线程可承载数千个虚拟线程
- 低开销:上下文切换由JVM管理而非操作系统
- 简化并发编程:使用同步代码风格实现异步效果
示例代码:
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
IntStream.range(0, 10_000).forEach(i -> {
executor.submit(() -> {
Thread.sleep(Duration.ofSeconds(1));
return i;
});
});
} // executor.close()会等待所有任务完成
实践建议:
- 对于I/O密集型应用,虚拟线程可以显著提高吞吐量
- 计算密集型任务仍需使用平台线程
五、同步机制深度解析
5.1 synchronized实现原理
对象头结构:
|-------------------------------------------------------|
| Mark Word (64 bits) | Klass Word (64 bits) |
|-------------------------------------------------------|
| unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 | lock:2 | OOP to metadata object |
|-------------------------------------------------------|
锁升级过程:
- 无锁状态
- 偏向锁:通过CAS设置ThreadID
- 轻量级锁:通过CAS自旋尝试获取锁
- 重量级锁:向操作系统申请互斥量
5.2 AQS框架原理
AbstractQueuedSynchronizer是Java并发包的核心基础组件。
核心结构:
// 简化的AQS核心结构
public abstract class AbstractQueuedSynchronizer {
private volatile int state; // 同步状态
private transient volatile Node head; // CLH队列头
private transient volatile Node tail; // CLH队列尾
static final class Node {
volatile int waitStatus;
volatile Node prev;
volatile Node next;
volatile Thread thread;
}
protected final boolean compareAndSetState(int expect, int update) {
// CAS操作更新state
}
}
实践建议:
- 理解AQS有助于自定义高性能同步器
- 大多数情况下应使用Java并发包提供的现成工具类
六、锁优化策略
6.1 常见优化技术
锁消除:JIT编译器通过逃逸分析移除不可能存在竞争的锁
public String concat(String s1, String s2) { StringBuffer sb = new StringBuffer(); sb.append(s1); sb.append(s2); return sb.toString(); }
锁粗化:将连续的锁请求合并为一个
// 优化前 for (int i = 0; i < 100; i++) { synchronized(lock) { // do something } } // 优化后 synchronized(lock) { for (int i = 0; i < 100; i++) { // do something } }
- 适应性自旋:根据历史成功率动态调整自旋次数
6.2 实践建议
- 优先使用
java.util.concurrent
包中的并发工具 - 减小锁粒度(如ConcurrentHashMap的分段锁)
- 读写分离(使用ReadWriteLock)
- 考虑使用无锁数据结构(如Atomic类)
- 监控锁竞争情况(jstack, JMC等工具)
七、总结与最佳实践
内存可见性:
- 使用volatile保证单个变量的可见性
- 使用synchronized或Lock保证代码块的可见性
线程安全设计:
// 线程安全计数器三种实现方式对比 // 1. synchronized方法 class Counter { private int value; public synchronized int increment() { return ++value; } } // 2. ReentrantLock class Counter { private int value; private final Lock lock = new ReentrantLock(); public int increment() { lock.lock(); try { return ++value; } finally { lock.unlock(); } } } // 3. AtomicInteger (最优) class Counter { private final AtomicInteger value = new AtomicInteger(); public int increment() { return value.incrementAndGet(); } }
性能考量:
- 低竞争场景:偏向锁/轻量级锁
- 中等竞争:适当自旋+CAS
- 高竞争:减少临界区大小或使用无锁算法
调试技巧:
# 查看锁竞争情况 jstack <pid> | grep -A 10 " contended " # 监控线程状态 jcmd <pid> Thread.print
通过深入理解JMM和多线程机制,开发者可以编写出既正确又高效的并发程序。随着Java虚拟线程的成熟,并发编程模型还将继续演进,值得持续关注。