一、Java内存模型(JMM):多线程通信的规则
JMM 是一种抽象的概念,它定义了线程和主内存之间的交互规则。
- 主内存(Main Memory) :所有线程共享的内存区域,对应 Java 中的堆内存。
- 工作内存(Working Memory) :每个线程私有的内存区域,对应 CPU 中的缓存(L1, L2) 和寄存器。
三大特性
- 原子性(Atomicity) :一个操作是不可分割的。
- 可见性(Visibility) :一个线程对共享变量的修改,对其他线程是立即可见的。
- 有序性(Ordering) :代码的执行顺序,与书写顺序一致。
二、硬件层支持:CPU缓存与内存屏障
JMM 的实现依赖于底层硬件。理解 CPU 的缓存结构和内存屏障是理解 JMM 的关键。
- CPU 缓存:CPU 缓存是线程私有的,这导致了可见性问题。
- 内存屏障(Memory Barrier) :一种CPU指令,用于强制阻止编译器和处理器对指令进行重排序,并刷新 CPU 缓存,从而解决可见性和有序性问题。
三、volatile
的底层实现:内存屏障与可见性
volatile
关键字能够保证变量的可见性和有序性,其底层原理是插入内存屏障。
-
写操作:在
volatile
变量的写操作之后,会插入一个StoreLoad
内存屏障。这个屏障的作用是:- 强制将线程工作内存中的值写回主内存。
- 使其他线程的缓存行无效,从而强制它们从主内存中重新读取。
-
读操作:在
volatile
变量的读操作之前,会插入一个LoadLoad
屏障。这保证了读操作总能获取到最新的值。
四、synchronized
的底层实现:锁升级与互斥
synchronized
关键字是 JVM 提供的重量级同步机制,其底层实现是基于**对象头(Mark Word)**中的锁状态。
-
锁升级路径:
- 无锁:对象创建时的初始状态。
- 偏向锁(Biased Locking) :当只有一个线程访问同步代码块时,JVM 会给对象头写入该线程的 ID,无需额外的同步开销。
- 轻量级锁(Lightweight Locking) :当出现竞争时,偏向锁会升级为轻量级锁。通过 CAS 操作在栈帧中创建锁记录。
- 重量级锁(Heavyweight Locking) :当轻量级锁的 CAS 操作失败,即出现激烈竞争时,锁会膨胀为重量级锁,进入操作系统级别的互斥,导致线程阻塞和唤醒的开销。
五、happens-before
原则:JMM的最终保证
happens-before
原则定义了操作之间的顺序和可见性。如果操作 A happens-before
操作 B,那么 A 的结果对 B 是可见的。
-
关键规则:
- 程序顺序规则:单线程内,代码书写顺序即为执行顺序。
- 锁规则:一个线程的解锁操作
happens-before
另一个线程的加锁操作。 volatile
变量规则:对一个volatile
变量的写操作happens-before
任何后续对它的读操作。
六、总结:JMM与并发编程的实践
volatile
:适用于一写多读的场景,能够保证可见性和有序性,但无法保证原子性。synchronized
:能够同时保证原子性、可见性、有序性,但性能开销较大。happens-before
:是理解并发编程的基石,它为我们提供了强大的内存可见性保证。