一、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.死锁如何避免?

  • 统一加锁顺序
  • 超时机制
  • 锁分离
  • 资源有序分配
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐