`volatile`和`synchronized`这两个Java常用关键字是如何借助内存屏障实现其同步语义

你希望深入理解JVM内存屏障的核心概念,同时具体掌握volatilesynchronized这两个Java常用关键字是如何借助内存屏障实现其同步语义的,我会从底层机制到实际应用示例,清晰拆解这部分内容。

一、JVM内存屏障核心回顾

内存屏障(Memory Barrier)是JVM基于CPU指令抽象出的底层机制,核心解决多线程场景下的两个问题:

  1. 指令重排序:禁止屏障两侧的指令被编译器/CPU重排,保证执行顺序;
  2. 内存可见性:强制缓存与主内存同步(写屏障刷缓存到主内存,读屏障从主内存加载数据)。

JVM定义了4种核心内存屏障(按开销从低到高):

屏障类型 核心作用
LoadLoad 禁止屏障前的读操作与屏障后的读操作重排序
StoreStore 禁止屏障前的写操作与屏障后的写操作重排序
LoadStore 禁止屏障前的读操作与屏障后的写操作重排序
StoreLoad 禁止屏障前的写操作与屏障后的读操作重排序(最重量级)

Java程序员无需直接操作内存屏障,JVM会为volatilesynchronized等关键字自动插入对应的屏障。

二、volatile如何利用内存屏障

volatile是轻量级同步关键字,能保证变量的可见性有序性(不保证原子性),其核心就是JVM为volatile变量的读写插入特定内存屏障。

1. volatile的内存屏障插入规则

操作类型 屏障插入位置 插入的屏障类型 核心目的
写volatile变量 写操作前 StoreStore屏障 保证普通变量的写操作先刷到主内存,再执行volatile写
写volatile变量 写操作后 StoreLoad屏障 保证volatile写刷到主内存,且后续读/写不重排到volatile写之前
读volatile变量 读操作前 LoadLoad屏障 保证普通变量的读操作完成后,再执行volatile读
读volatile变量 读操作后 LoadStore屏障 保证volatile读完成后,后续写操作不重排到volatile读之前

2. 实战示例1:volatile解决单例模式指令重排序问题

这是volatile最经典的应用场景,核心解决“双重检查锁(DCL)”中对象半初始化问题:

public class Singleton {
    // 必须加volatile!否则可能出现指令重排序导致空指针
    private volatile static Singleton instance;

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) { // 第一次检查:避免不必要的锁
            synchronized (Singleton.class) {
                if (instance == null) { // 第二次检查:防止并发创建
                    // new操作的实际指令分3步(不加volatile会重排):
                    // 1. 分配内存 → 2. 初始化对象 → 3. 给instance赋值
                    // 重排后可能变成 1→3→2,导致其他线程读到"非null但未初始化"的instance
                    // 加volatile后,StoreStore/StoreLoad屏障禁止这种重排序
                    instance = new Singleton(); 
                }
            }
        }
        return instance;
    }
}

屏障作用解析

  • instanceinstance = new Singleton())时,JVM先插入StoreStore屏障,禁止“初始化对象”和“赋值”重排;
  • 再插入StoreLoad屏障,保证赋值完成后,其他线程读instance能拿到主内存的最新值。

3. 实战示例2:volatile读写的屏障执行流程

public class VolatileDemo {
    private volatile int flag = 0; // volatile变量
    private int normalNum = 0;     // 普通变量

    // 写volatile:触发StoreStore + StoreLoad屏障
    public void write() {
        normalNum = 10; // 普通写
        // 插入StoreStore屏障:保证normalNum的写先刷到主内存,再执行flag的写
        flag = 1;       // volatile写
        // 插入StoreLoad屏障:保证flag的写刷到主内存,后续操作不重排到这里之前
    }

    // 读volatile:触发LoadLoad + LoadStore屏障
    public void read() {
        // 插入LoadLoad屏障:保证之前的读操作完成,再读flag
        int temp = flag; // volatile读
        // 插入LoadStore屏障:保证读flag完成后,再执行normalNum的写
        normalNum = temp + 1; // 普通写
    }
}

三、synchronized如何利用内存屏障

synchronized是重量级同步机制,能保证原子性、可见性、有序性,其内存屏障插入逻辑覆盖整个同步块,比volatile更全面。

1. synchronized的内存屏障插入规则

synchronized的屏障插入分为“进入同步块”和“退出同步块”两个阶段:

操作阶段 屏障插入位置 插入的屏障类型及作用
进入同步块 获取锁后、执行块内代码前 LoadLoad + LoadStore屏障:
1. 禁止块内操作重排到获取锁之前;
2. 强制从主内存加载共享变量最新值
退出同步块 释放锁前、执行块内代码后 StoreStore + StoreLoad屏障:
1. 禁止块内操作重排到释放锁之后;
2. 强制将块内修改刷到主内存

2. 实战示例:synchronized的屏障效果

public class SynchronizedDemo {
    private int count = 0; // 普通共享变量

    // 同步方法(等价于synchronized(this))
    public synchronized void increment() {
        // 进入同步块:JVM插入LoadLoad + LoadStore屏障
        // 1. 强制从主内存读取count的最新值(可见性)
        // 2. 禁止count++重排到获取锁之前(有序性)
        count++; // 原子性:同步块保证该操作不会被其他线程打断
        // 退出同步块:JVM插入StoreStore + StoreLoad屏障
        // 1. 强制将count的修改刷到主内存(可见性)
        // 2. 禁止count++重排到释放锁之后(有序性)
    }

    // 同步代码块
    public void decrement() {
        synchronized (this) {
            // 进入/退出时的屏障逻辑与同步方法完全一致
            count--;
        }
    }
}

核心效果

  • 线程1执行完synchronized块后,修改的count会通过StoreStore/StoreLoad屏障刷到主内存;
  • 线程2进入同一个synchronized块时,通过LoadLoad/LoadStore屏障从主内存读取最新的count值,保证多线程数据一致。

3. volatile vs synchronized 内存屏障对比

特性 volatile synchronized
屏障作用范围 仅针对自身变量的读写 覆盖整个同步块的所有操作
性能开销 轻量级(无锁竞争) 重量级(可能触发上下文切换)
保证的语义 可见性、有序性(无原子性) 原子性、可见性、有序性

总结

  1. 内存屏障是JVM解决重排序和可见性的底层核心,分为4种类型,StoreLoad屏障开销最大;
  2. volatile通过在变量读写时插入特定屏障,实现可见性和有序性,典型场景是DCL单例模式;
  3. synchronized通过在同步块进入/退出时插入组合屏障,覆盖块内所有操作,实现全量同步语义(原子性+可见性+有序性)。

关键点回顾

  • volatile的屏障仅作用于自身变量,轻量但不保证原子性;
  • synchronized的屏障覆盖整个同步块,重量级但能保证原子性;
  • 两者均由JVM自动插入内存屏障,无需程序员手动操作。
posted @ 2026-01-30 17:52  先弓  阅读(2)  评论(0)    收藏  举报