1. 引言

在多线程编程中,共享变量的可见性和同步问题一直是开发者面临的挑战。Java 提供了 volatile 关键字来确保变量的可见性有序性,但它并不保证原子性。本文将深入探讨 volatile 的工作原理,包括:

  • 高速缓存(CPU Cache)和主内存(Main Memory)的同步时机
  • 内存屏障(Memory Barrier)的作用
  • volatile 的适用场景与限制
  • 底层硬件(如 MESI 协议)如何支持 volatile 语义

最后,我们会通过 示例代码内存模型图示 来直观理解 volatile 的行为。


2. volatile 的核心作用

volatile 主要解决两个问题:

  1. 可见性问题:确保一个线程对变量的修改能立即被其他线程看到。
  2. 有序性问题:防止 JVM 和 CPU 对指令进行不合理的重排序。

但它 不保证原子性(如 i++ 这样的复合操作仍然需要额外的同步机制)。


3. volatile 的同步机制

3.1 何时同步?

Java 内存模型(JMM)规定,volatile 变量的读写遵循严格的规则:

  • 写操作(Write)

    • 当线程写入 volatile 变量时,JVM 会 立即 将该值刷新到主内存(而不是仅停留在 CPU 缓存)。
    • 为了保证立即刷新,JVM 会在写操作后插入 StoreLoad 内存屏障(或等效指令),强制 CPU 将数据写回主内存,并确保后续读操作能看到最新值。
  • 读操作(Read)

    • 当线程读取 volatile 变量时,JVM 会 强制 从主内存加载最新值(而不是使用本地缓存的旧值)。
    • 为了保证读取最新值,JVM 会在读操作前插入 LoadLoad + LoadStore 内存屏障(或等效指令),使当前 CPU 缓存失效并重新加载数据。

3.2 同步流程图

+-------------------+       +-------------------+       +-------------------+
| Thread 1 | | Main Memory | | Thread 2 |
| (CPU Core 1) | | | | (CPU Core 2) |
+-------------------+ +-------------------+ +-------------------+
| | | | | |
| volatile x = 1; | ----> | x = 1 (最新值) | <---- | int y = x; |
| | | | | (读取最新值) |
+-------------------+ +-------------------+ +-------------------+
  • Thread 1 写入 volatile x = 1

    • 值立即写入主内存,而不是仅停留在 Core 1 的缓存。
  • Thread 2 读取 volatile x
    • 强制从主内存加载最新值,而不是使用 Core 2 缓存中的旧值。

4. 内存屏障(Memory Barrier)的作用

内存屏障是 CPU 或 JVM 插入的特殊指令,用于控制指令执行顺序和缓存一致性。volatile 依赖内存屏障实现其语义:

屏障类型 作用
StoreStore 确保 volatile 写之前的普通写操作先完成
StoreLoad 确保 volatile 写完成后,后续读操作能看到最新值
LoadLoad 确保 volatile 读之前的普通读操作先完成
LoadStore 确保 volatile 读完成后,后续写操作不会重排序到读之前

volatile 写操作后的 StoreLoad 屏障是最严格的,因为它强制刷新所有缓存数据到主内存,确保后续读操作能获取最新值。


5. 底层硬件支持(MESI 协议)

现代 CPU 使用 缓存一致性协议(如 MESI)来维护多核缓存的一致性。volatile 的内存屏障会触发 CPU 执行必要的缓存同步操作:

  • MESI 状态

    • Modified (M):当前 CPU 缓存的数据已被修改,与主内存不一致。
    • Exclusive (E):当前 CPU 独占缓存行,数据与主内存一致。
    • Shared (S):多个 CPU 共享缓存行,数据与主内存一致。
    • Invalid (I):缓存行无效,必须从主内存重新加载。

volatile 写操作

  1. 当前 CPU 将缓存行标记为 Modified (M)
  2. 其他 CPU 的缓存行被标记为 Invalid (I),强制它们下次读取时重新加载。

volatile 读操作

  1. 如果缓存行状态为 Invalid (I),则从主内存加载最新值。
  2. 否则,直接从缓存读取(但 volatile 强制读屏障,通常会使缓存失效)。

6. volatile 的适用场景与限制

6.1 适用场景

  • 状态标志(如 boolean running
    volatile boolean running = true;
    
    void stop() { running = false; }
    void doWork() { while (running) { ... } }
  • 单次写入、多次读取(如配置变量)
    volatile Config config = loadConfig();

6.2 不适用场景

  • 复合操作(如 i++
    volatile int count = 0;
    count++; // 非原子操作,仍可能发生竞态条件

    应改用 AtomicIntegersynchronized


7. 总结

特性 volatile synchronized AtomicXXX
可见性
有序性
原子性
适用场景 状态标志、单次写入 复杂同步 计数器等

关键结论:

  1. volatile 保证 写操作后立即同步到主内存读操作前强制从主内存加载
  2. 通过 内存屏障 实现,避免指令重排序。
  3. 不保证原子性,复合操作仍需额外同步。
  4. 底层依赖 MESI 协议 维护缓存一致性。

8. 扩展思考

  • volatile vs finalfinal 变量在构造函数完成后对所有线程可见,但之后不能修改。
  • volatile 在单例模式(DCL)中的应用
    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 防止指令重排序,避免返回未初始化的对象。


希望这篇博客能帮助你彻底理解 volatile 的机制!如果有疑问或建议,欢迎在评论区讨论。

【深入理解 volatile】内存可见性与同步机制详解的更多相关文章

  1. 转载:futex同步机制详解

    在编译2.6内核的时候,你会在编译选项中看到[*] Enable futex support这一项,上网查,有的资料会告诉你"不选这个内核不一定能正确的运行使用glibc的程序", ...

  2. java synchronized 线程同步机制详解

    Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码. 一.当两个并发线程访问同一个对象object中的这个synchronized(this ...

  3. 《深入理解mybatis原理》 Mybatis初始化机制详解

    对于任何框架而言,在使用前都要进行一系列的初始化,MyBatis也不例外.本章将通过以下几点详细介绍MyBatis的初始化过程. 1.MyBatis的初始化做了什么 2. MyBatis基于XML配置 ...

  4. 深入理解mybatis原理, Mybatis初始化SqlSessionFactory机制详解(转)

    文章转自http://blog.csdn.net/l454822901/article/details/51829785 对于任何框架而言,在使用前都要进行一系列的初始化,MyBatis也不例外.本章 ...

  5. 《深入理解mybatis原理2》 Mybatis初始化机制详解

    <深入理解mybatis原理> Mybatis初始化机制详解 对于任何框架而言,在使用前都要进行一系列的初始化,MyBatis也不例外.本章将通过以下几点详细介绍MyBatis的初始化过程 ...

  6. Linux 内存机制详解宝典

    Linux 内存机制详解宝典 在linux的内存分配机制中,优先使用物理内存,当物理内存还有空闲时(还够用),不会释放其占用内存,就算占用内存的程序已经被关闭了,该程序所占用的内存用来做缓存使用,对于 ...

  7. 浏览器 HTTP 协议缓存机制详解

    最近在准备优化日志请求时遇到了一些令人疑惑的问题,比如为什么响应头里出现了两个 cache control.为什么明明设置了 no cache 却还是发请求,为什么多次访问时有时请求里带了 etag, ...

  8. JVM的垃圾回收机制详解和调优

    JVM的垃圾回收机制详解和调优 gc即垃圾收集机制是指jvm用于释放那些不再使用的对象所占用的内存.java语言并不要求jvm有gc,也没有规定gc如何工作.不过常用的jvm都有gc,而且大多数gc都 ...

  9. PHP的垃圾回收机制详解

    原文:PHP的垃圾回收机制详解 最近由于使用php编写了一个脚本,模拟实现了一个守护进程,因此需要深入理解php中的垃圾回收机制.本文参考了PHP手册. 在理解PHP垃圾回收机制(GC)之前,先了解一 ...

  10. Java 反射 设计模式 动态代理机制详解 [ 转载 ]

    Java 反射 设计模式 动态代理机制详解 [ 转载 ] @author 亦山 原文链接:http://blog.csdn.net/luanlouis/article/details/24589193 ...

随机推荐

  1. 【SpringCloud】zuul路由网关

    zuul路由网关 概述描述 路由基本配置 路由访问映射规则 查看路由信息 过滤器 太老旧了,就不做了解了

  2. win11开启hyper-v安装windows虚拟机

    序言: 这周要居家办公了, 趁周末赶紧配置办公环境,因为公司注重安全保密,所以要用对应安全软件,我就干脆整台虚拟机来运行这些东西吧. 开启hyper-v服务 好像是由于电脑装的是windows家庭版, ...

  3. STM32_RTOS_V2编程模板1-消息队列

    #pragma region QUEUE1 // 1DEFINE osMessageQueueId_t queueDemo1 = NULL; // 2INIT queueDemo1 = osMessa ...

  4. 多线程,Join()

    一.定义:就是该线程是指的主线程等待子线程的终止.也就是在子线程调用了join()方法,后面的代码,只有等到子线程结束了才能执行 二.不加join: class Thread1 extends Thr ...

  5. app自动化的元素操作api

    1.click() 触发当前元素的点击事件 elelogin.click(); 2.sendKeys(String str) 往触发的当前元素输入数据 eleinputpwd.sendKeys(&qu ...

  6. Flex布局教程:语法篇--css中的display:Flex

    先用一句话秒懂display:Flex;这句css代码,如图: 放个目录先: 目录 一.Flex布局是什么? 二.基本概念 三.容器的属性 3.1 flex-direction属性(主轴的方向) 3. ...

  7. 前端自动打包工具webpack的安装和使用

    一.准备 要使用webpack工具,最好了解一些基础的文件目录操作的命令行, win all里的一些常用的命令行 http://blog.csdn.net/qq_36110571/article/de ...

  8. HarmonyOS运动开发:如何集成百度地图SDK、运动跟随与运动公里数记录

    前言 在开发运动类应用时,集成地图功能以及实时记录运动轨迹和公里数是核心需求之一.本文将详细介绍如何在 HarmonyOS 应用中集成百度地图 SDK,实现运动跟随以及运动公里数的记录. 一.集成百度 ...

  9. 【SpringBoot异步导入Excel实战】从设计到优化的完整解决方案

    SpringBoot异步导入Excel实战:从设计到优化的完整解决方案 一.背景与需求 在企业级应用中,Excel导入是常见需求.当导入数据量较大时,同步处理可能导致接口阻塞,影响用户体验.本文结合S ...

  10. TVM:visitor设计模式

    visitor模式,因为它在编译器的框架中应用的广泛,在TVM中也是无处不在. visitor模式介绍 Visitor(访问者)模式的定义:将作用于某种数据结构中的各元素的操作分离出来封装成独立的类, ...