【JUC/JMM 并发核心体系】
一、JMM(Java Memory Model,Java 内存模型)
1.本质定义
JMM不是物理内存模型,而是并发语义规范模型,定义:
- 线程如何在主内存交互
- 变量在工作内存(线程本地缓存)与主内存之间的可见性规则
- 指令重排序规则
- 并发下happens-before 关系
内存结构抽象模型:
主内存(Heap + Method Area 中的共享变量)
↑ ↓
线程A工作内存 线程B工作内存
(本地副本) (本地副本)
2.三大核心问题
(1)可见性问题
一个线程修改变量,另一个线程是否可见
(2)原子性问题
操作是否不可分割
(3)有序性问题
代码执行顺序是否与编写顺序一致
3.happens-before 规则(并发程序“因果律”)
核心规则:
| 规则 | 含义 |
|---|---|
| 程序次序规则 | 单线程内按代码顺序执行 |
| volatile | volatile写 -> 读可见 |
| 锁规则 | unlock -> lock |
| 传递性 | A -> B,B -> C,则A -> C |
| 线程启动 | start()先行于线程执行 |
| 线程终止 | 线程结束先行于join() |
二、volatile
1.volatile三大语义
(1)可见性
写volatile变量 -> 立即刷新主内存
读volatile变量 -> 直接从主内存读
(2)有序性(禁止指令重排)
通过内存屏障(Memory Barrier)**实现
(3)不保证原子性
i++依然线程不安全
2.volatile底层实现机制
2.1 JVM层面
- 写volatile:插入StoreStore + StoreLoad 屏障
- 读volatile:插入 LoadLoad + LoadStore 屏障
2.2 CPU层面
- MESI缓存一致性协议
- 缓存行失效机制
- 总线嗅探
3.volatile经典使用场景
状态标志位(中断/停止线程):
volatile boolean running = true;
while(running){
// 执行任务
}
双重检查锁(DCL 单例):
private static volatile Singleton instance;
三、彻底玩转单例模式
1.线程不安全写法
class Singleton {
private static Singleton instance;
public static Singleton getInstance() {
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
多线程下会创建多个对象
2.synchronized 版本(安全但慢)
public static synchronized Singleton getInstance() {
if(instance == null){
instance = new Singleton();
}
return instance;
}
性能瓶颈
3.DCL + volatile
class Singleton {
private static volatile Singleton instance;
public static Singleton getInstance() {
if(instance == null){
synchronized(Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
为什么必须volatile?
防止指令重排
1. 分配内存
2. 初始化对象
3. 指向内存地址
可能重排为:
1. 分配内存
3. 指向内存
2. 初始化对象
-> 其他线程读到半初始化对象
4.最优方案(静态内部类)
class Singleton {
private Singleton(){}
private static class Holder{
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance(){
return Holder.INSTANCE;
}
}
原理:
- 类加载机制保证线程安全
- 懒加载
- 无锁
- 高性能
四、CAS(Compare And Swap)深度理解
1.本质定义
无锁原子操作:
CAS(V, E, N)
如果 V == E,则 V = N
否则失败
2.Java体现
Unsafe.compareAndSwapInt(...)
AtomicInteger.compareAndSet(...)
3.底层实现
CPU指令:
x86:lock cmpxchg
ARM:ldrex/strex
4.CAS 的三大问题
(1)ABA问题
值 A → B → A,CAS 认为没变
(2)自旋开销大
高并发下 CPU 空转
(3)只能保证一个变量原子性
五、原子引用
1.解决ABA问题
AtomicStampedReference
AtomicStampedReference<String> ref =
new AtomicStampedReference<>("A", 1);
版本号机制(类似乐观锁)
AtomicMarkableReference
布尔标记版本控制
2.应用场景
- 无锁队列
- 无锁栈
- 并发对象替换
- 状态机流转
六、锁理解
1.公平锁 vs 非公平锁
公平锁
new ReentrantLock(true);
- 先来先得
- 排队机制
- 吞吐量低
- 延迟稳定
非公平锁
new ReentrantLock(false);
- 抢占式
- 吞吐量高
- 延迟波动大
2.可重入锁
同一线程可多次获取同一把锁
lock.lock();
lock.lock(); // 不会死锁
原理:线程持有计数器(state + owner)
3.自旋锁
线程不挂起,一直循环等待锁释放
CAS + while
while(!cas()){}
不用if防止虚假唤醒
适用场景:
- 锁持有时间极短
- CPU核心数多
- 高并发低阻塞
- JDK内部大量使用(偏向锁、轻量级锁)
4.死锁
四大必要条件:
- 互斥
- 请求与保持
- 不可剥夺
- 循环等待
synchronized(A){
synchronized(B){}
}
synchronized(B){
synchronized(A){}
}
总结
1.volatile 能代替 synchronized 吗?
不能。
volatile:可见性 + 有序性
synchronized:可见性 + 原子性 + 有序性
2.为什么 DCL 必须加 volatile?
防止指令重排导致“半初始化对象”被其他线程访问
3.CAS 为什么性能高?
- 不阻塞线程
- 无上下文切换
- CPU原子指令级支持
4.ABA 问题怎么解决?
AtomicStampedReference(版本号机制)
5.自旋锁一定快吗?
不一定。高冲突 → CPU 空转 → 性能雪崩
6.死锁如何避免?
- 统一加锁顺序
- 超时机制
- 锁分离
- 资源有序分配
更多推荐




所有评论(0)