当前位置: 首页 > news >正文

JUC 并发编程:对可见性、有序性与 volatile的理解

一、核心概念:可见性、有序性

在 Java 多线程编程中,可见性有序性是并发问题的核心痛点,也是 Java 内存模型(JMM)需要解决的核心问题,JUC 并发包的诸多特性均围绕这两个问题设计,而volatile是 JMM 提供的轻量级解决手段。

1. 可见性

指一个线程对共享变量的修改,能够及时被其他线程感知到。

  • 问题根源:JMM 中每个线程有自己的工作内存,共享变量存储在主内存,线程操作共享变量时会先拷贝到工作内存,修改后再刷回主内存,若未及时刷回,其他线程读取的仍是旧值,导致脏读
  • 无保障场景:普通共享变量的写操作,JMM 不保证其对其他线程的读操作可见。

2. 有序性

指程序执行的顺序按照代码的书写顺序执行,避免指令重排序

  • 问题根源:JVM 和 CPU 为了优化执行效率,会在不影响单线程执行结果的前提下,对指令进行重排序(编译器重排序、CPU 重排序),但多线程环境下,重排序会导致执行结果异常。
  • 关键原则:happens-before规则是 JMM 定义的有序性和可见性的核心保障,满足该规则的操作,前序操作的结果对后续操作可见,且执行顺序有序。

二、volatile 基础使用

1. volatile 定义

volatile是 Java 的关键字,用于修饰共享变量,是 JMM 提供的轻量级同步机制,不保证原子性,但能同时保证可见性有序性,底层通过内存屏障禁止指令重排序实现。

2. volatile 核心特性

(1)保证可见性

volatile变量的写操作,会立即将工作内存中的修改刷回主内存;对volatile变量的读操作,会直接从主内存读取最新值,清空工作内存的旧值,避免脏读。

(2)保证有序性

禁止 JVM 和 CPU 对volatile变量相关的指令进行重排序,通过在volatile写 / 读操作前后插入内存屏障,固定指令执行顺序。

(3)不保证原子性

volatile无法解决多线程同时写操作的竞态问题,例如i++(读 - 改 - 写)操作,多线程执行时仍会出现数据不一致,需配合锁(synchronized/Lock)或原子类(AtomicInteger)使用。

3. volatile 基本语法

// 修饰成员变量 volatile static int num = 0; // 修饰对象引用 volatile static User user = null; // 修饰实例变量 class Demo { volatile int flag = false; }

4. 基础使用场景:状态标记位

这是volatile最典型的应用场景,用于多线程间的状态通知,保证状态的即时可见。

public class VolatileBasicDemo { // volatile修饰状态标记,保证多线程间可见 private static volatile boolean isRunning = true; public static void main(String[] args) throws InterruptedException { // 线程1:执行业务逻辑,根据标记位停止 new Thread(() -> { int i = 0; while (isRunning) { i++; } System.out.println("线程1停止,累计执行:" + i + "次"); }, "t1").start(); // 主线程休眠1秒,让线程1先执行 Thread.sleep(1000); // 修改标记位,线程1能立即感知并停止 isRunning = false; System.out.println("主线程修改标记位:isRunning = false"); } }

结果:主线程修改isRunning后,线程 1 会立即退出循环,体现volatile的可见性。

三、volatile 与 happens-before 规则

volatile的可见性和有序性,通过happens-before中的volatile 变量规则传递性规则保障,是其底层核心逻辑。

1. volatile 变量规则

对一个volatile变量的写操作,happens-before 于后续对该变量的读操作

  • 核心:volatile写的结果,对所有后续的volatile读可见,且写操作执行顺序早于读操作。

2. 传递性规则结合使用

若操作 A happens-before 操作 B,操作 B happens-before 操作 C,则 A happens-before 操作 C。

  • volatile常与程序顺序规则结合,通过传递性实现普通变量的可见性,示例:
public class VolatileHappensBeforeDemo { private static volatile boolean flag = false; private static int num = 0; // 普通变量 public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { num = 100; // 操作1:普通写(程序顺序规则:1 happens-before 2) flag = true; // 操作2:volatile写(volatile规则:2 happens-before 3) }, "t1"); Thread t2 = new Thread(() -> { if (flag) { // 操作3:volatile读(程序顺序规则:3 happens-before 4) System.out.println(num); // 操作4:普通读,一定输出100 } }, "t2"); t1.start(); t2.start(); t1.join(); t2.join(); } }

解释:通过传递性,num=100(操作 1)happens-beforeSystem.out.println(num)(操作 4),实现了普通变量的跨线程可见性。

四、volatile 进阶使用

1. 进阶场景 1:双重检查锁定(DCL)实现单例模式

单例模式的懒汉式实现中,多线程环境下会出现指令重排序导致的对象初始化不完整问题,volatile可解决该问题,是 DCL 单例的核心保障。

(1)无 volatile 的 DCL 问题
// 有问题的懒汉式单例(多线程下可能获取到未初始化完成的对象) class Singleton { private static Singleton instance = null; // 未加volatile private Singleton() {} public static Singleton getInstance() { if (instance == null) { // 第一次检查 synchronized (Singleton.class) { // 加锁 if (instance == null) { // 第二次检查 instance = new Singleton(); // 存在指令重排序 } } } return instance; } }

问题根源instance = new Singleton()分为 3 步,JVM 会重排序:

  1. 分配对象内存空间;2. 初始化对象;3. 将 instance 指向分配的内存地址。重排序后可能出现1→3→2,此时其他线程第一次检查会发现instance != null,直接返回未初始化的对象。
(2)加 volatile 的正确 DCL 单例
// 正确的DCL单例模式(volatile禁止指令重排序) class Singleton { // volatile修饰实例引用,禁止指令重排序 private static volatile Singleton instance = null; private Singleton() {} public static Singleton getInstance() { if (instance == null) { // 第一次检查,无锁,提高效率 synchronized (Singleton.class) { // 类锁,保证单例 if (instance == null) { // 第二次检查,防止多线程重复创建 instance = new Singleton(); } } } return instance; } }

核心volatile禁止instance = new Singleton()的指令重排序,保证步骤1→2→3执行,确保其他线程看到的instance一定是初始化完成的对象。

2. 进阶场景 2:配合原子类实现高效并发

volatile不保证原子性,但若与JUC 原子类Atomic*)结合,可实现高效的原子操作 + 可见性,替代重量级锁。

import java.util.concurrent.atomic.AtomicInteger; public class VolatileAtomicDemo { // 原子类保证原子性,volatile保证可见性(原子类底层本身通过volatile修饰变量) private static volatile AtomicInteger count = new AtomicInteger(0); public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { for (int i = 0; i < 1000; i++) { count.incrementAndGet(); // 原子自增,替代i++ } }, "t1"); Thread t2 = new Thread(() -> { for (int i = 0; i < 1000; i++) { count.incrementAndGet(); } }, "t2"); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println("最终计数:" + count.get()); // 一定输出2000 } }

说明:JUC 原子类(如AtomicInteger)的底层变量通过volatile修饰,结合 CAS 算法实现原子操作,同时保证可见性。

3. 进阶场景 3:解决普通变量的跨线程可见性

通过volatile的传递性规则,实现普通共享变量的跨线程可见,替代synchronized(轻量级,效率更高),示例参考二、4VolatileHappensBeforeDemo

五、volatile 与 synchronized 对比

volatilesynchronized都是解决并发可见性和有序性的手段,但特性和使用场景差异显著,volatile 是轻量级同步,synchronized 是重量级同步,对比如下:

特性volatilesynchronized
修饰范围仅能修饰成员变量 / 实例变量可修饰方法、代码块、静态方法
可见性保证保证
有序性保证保证(通过监视器锁规则)
原子性不保证保证(独占锁,串行执行)
性能轻量级,无锁,开销极小重量级,有锁竞争,开销大
阻塞性非阻塞式同步阻塞式同步(锁等待)

核心使用原则

  • 若仅需状态标记单例 DCL普通变量的可见性,使用volatile,效率更高;
  • 若需原子操作(如i++)、多步操作的同步,使用synchronized或 JUC 的Lock、原子类;
  • volatile可作为synchronized的补充,而非替代。

六、学习总结

1. 核心总结

  • volatile的核心价值:轻量级保证可见性和有序性,是 JMM 中最常用的同步手段之一;
  • volatile的底层实现:内存屏障(禁止重排序)+主内存直读直写(保证可见性);
  • volatile的局限性:不保证原子性,无法解决多线程写操作的竞态问题;
  • 关键关联:volatile的特性通过happens-beforevolatile 变量规则传递性规则保障,是 JUC 并发编程的基础。

2. 实用使用建议

  1. 优先使用volatile作为状态标记位,这是其最安全、最典型的场景;
  2. 实现DCL 单例模式时,必须给实例引用加volatile,禁止指令重排序;
  3. 不要用volatile修饰需要多线程原子写的变量,需配合原子类或锁;
  4. 利用volatile的传递性,可实现普通变量的跨线程可见,替代重量级锁;
  5. JUC 原子类底层已使用volatile,无需额外修饰,直接使用即可;
  6. 区分可见性 / 有序性和原子性:若问题是读不到最新值/指令重排序,用volatile;若问题是多线程写冲突,用锁 / 原子类。
http://www.cnnetsun.cn/news/1261024.html

相关文章:

  • RAG检索瓶颈突破实战指南(非常详细),Multi-HyDE与Adaptive HyDE从入门到精通,收藏这一篇就够了!
  • 实现一下简单的聊天功能,包括聊天消息自适应大小
  • KohakuRAG:层次化RAG的新范式
  • 关于DBeaver的一些配置
  • 性能优化-前端性能优化相关
  • asp毕业设计——基于asp+access的网上选题系统设计与实现(毕业论文+程序源码)——网上选题系统
  • 【OS】操作系统分类及用户界面
  • 探索 Stanford Alpaca: 一个强大的深度学习框架
  • 探索超轻量级人脸识别神器:Ultra Light Fast Generic Face Detector 1MB
  • 【亲测免费】 推荐一款强大的数据可视化库——Plotly.js
  • MAPPO动作类型改进(二)——MAPPO+连续环境
  • IPED元数据可视化案例:创建取证报告中的元数据图表
  • Apache Airflow 项目教程
  • Carbon 开源项目指南
  • YTKNetwork批量请求终极指南:YTKBatchRequest高效应用实战
  • OpenCart 开源电商系统推荐
  • 新手到大卖都在用!亚马逊运营全流程工具盘点
  • 开源项目推荐:ccv
  • YTKNetwork性能优化终极指南:10个提升iOS网络请求效率的实用方法
  • 深入理解粤语编程编译器:从Python转换到LLVM执行
  • LCD屏幕调光方式全解析:DC调光 vs PWM调光,哪种更适合你的眼睛?
  • DS1302实时时钟模块从入门到精通:手把手教你搭建可调时钟系统
  • 用Multisim仿真8种经典运放电路:手把手教你搭建比例/微分/积分放大器
  • Android11音频架构剖析:RK3568平台如何通过RK809实现智能功放切换
  • 告别DHCP!CentOS服务器静态IP设置避坑指南(附常见问题解决方案)
  • 2026年广东省职业院校技能大赛(高职组)移动应用设计与开发赛项样题(一)
  • 从土壤质地到农作物适配:HWSD数据库在精准农业中的5个典型应用场景
  • PostgreSQL17 Windows版安装包下载全攻略:官网直链+镜像加速
  • FastAPI实战:5分钟搞定即梦AI文生视频API逆向(附完整代码)
  • 2023最新版:用夜神模拟器安卓7抓包微信小程序的3个关键配置