Java内存模型的迷雾——从 happens-before 到 volatile 的可见性保证

简介: Java内存模型是Java并发编程的基石,也是最容易被误解的领域之一。

Java内存模型是Java并发编程的基石,也是最容易被误解的领域之一。它定义了多线程程序中共享变量的访问规则,回答了这样一个核心问题:当一个线程修改了共享变量,其他线程何时能看到这个修改?理解Java内存模型,不仅是写出正确并发程序的前提,也是理解volatile、synchronized、final等关键字语义的关键。
参考:https://xbivx.cn/category/travel-advice.html

Java内存模型的核心是happens-before关系。如果两个操作之间存在happens-before关系,那么第一个操作的结果对第二个操作可见,且第一个操作的执行顺序排在第二个操作之前。happens-before规则包括:程序顺序规则(同一个线程中,写在前面的操作happens-before后面的操作)、监视器锁规则(对一个锁的解锁happens-before随后对同一个锁的加锁)、volatile变量规则(对volatile变量的写happens-before随后对同一个volatile变量的读)、线程启动规则(线程的start调用happens-before该线程中的任何操作)、线程终止规则(线程中的任何操作happens-before其他线程检测到该线程终止)等。

volatile关键字是Java中最轻量级的同步机制。它保证了对volatile变量的读写具有可见性——写入volatile变量的值立即对其他线程可见。但volatile不保证原子性,复合操作如count++在volatile变量上仍然不是线程安全的。volatile的底层实现涉及内存屏障——编译器在生成volatile读写指令时插入特殊的屏障,禁止某些重排序,并强制刷新CPU缓存到主内存。

与C++的volatile不同,Java的volatile不用于硬件映射变量,而是专门用于多线程通信。一个常见的误区是认为volatile可以替代锁。实际上,volatile只适用于以下场景:写入变量不依赖于当前值(如设置标志位)、该变量不与其他状态变量共同构成不变量。其他情况都需要使用锁或原子类。
参考:https://xbivx.cn/category/disaster-warning.html

synchronized关键字提供了更强大的同步语义。它保证互斥(同一时间只有一个线程可以执行同步块)和可见性(解锁前对共享变量的修改对后续加锁的线程可见)。synchronized可以用于实例方法、静态方法和代码块。JVM对synchronized进行了大量优化:偏向锁、轻量级锁、重量级锁的自适应升级,以及锁消除和锁粗化等编译器优化。

final字段在Java内存模型中有特殊语义。在构造函数中正确初始化后的final字段,在其他线程中可见时一定处于已初始化状态,无需同步。这为不可变对象的安全发布提供了保证。但注意,如果final字段指向一个可变对象,该对象内部的状态仍然需要额外同步。

双重检查锁定的陷阱是Java并发编程中的经典反模式。在没有volatile修饰的情况下,双重检查锁定可能返回未完全初始化的对象,因为构造函数和引用赋值可能被重排序。Java 5之后,通过将单例变量声明为volatile修复了这个问题,但更好的替代方案是使用静态内部类或枚举单例。

Java内存模型允许编译器、处理器和运行时对指令进行重排序,只要不改变单线程程序的语义。这种重排序是性能优化的关键,但在多线程环境下可能导致违反直觉的结果。例如,在没有同步的情况下,一个线程写入两个变量的顺序,在其他线程看来可能被颠倒。happens-before规则就是用来禁止这种危险重排序的。

现代CPU的缓存架构加剧了可见性问题。每个CPU核心都有自己的L1、L2缓存,对共享变量的修改可能停留在缓存中,未刷新到主内存。Java内存模型要求JVM在必要时插入内存屏障,强制缓存一致性协议(如MESI)进行同步。但内存屏障是有成本的,过度使用会损害性能。
参考:https://xbivx.cn/category/weather-knowledge.html

发布与逸出是另一个重要概念。对象的安全发布意味着其他线程看到的对象处于完全构造且一致的状态。不安全的发布可能导致其他线程看到部分构造的对象(如未初始化的字段)。安全发布的方法包括:在静态初始化器中初始化对象引用、将引用存储在volatile字段或AtomicReference中、将引用存储在正确构造的对象的final字段中、或者使用锁来保护访问。

Java内存模型的理论基础来自顺序一致性。顺序一致性是一种理想的内存模型,其中所有操作按照程序顺序执行,且所有线程看到的操作顺序相同。Java内存模型允许比顺序一致性更多的重排序,以获得更好的性能,但通过happens-before规则限制了重排序的范围,确保正确同步的程序表现出顺序一致的行为。

对于大多数开发者来说,不需要深入理解Java内存模型的每一个细节。遵循以下原则就足以写出正确的并发程序:尽可能使用java.util.concurrent包中的高级并发工具;使用volatile只做标志位;使用synchronized或Lock保护共享变量的所有访问;尽量使用不可变对象;以及通过JCStress等工具进行并发测试。
参考:https://xbivx.cn

目录
相关文章
|
2月前
|
存储 缓存 安全
synchronized 底层全解:从对象头、锁升级到内核实现,击穿并发编程的核心基石
本文深度剖析Java中synchronized的底层原理:从三种使用范式、字节码实现,到对象内存布局、Mark Word状态切换,详解锁升级(偏向→轻量→重量)全流程及JVM优化(锁消除/粗化),并结合JOL实战验证,兼顾理论深度与生产实用性。
437 2
|
1月前
|
SQL 存储 缓存
深入拆解 Java volatile:从内存屏障到无锁编程的实战指南
volatile是Java并发编程核心关键字,通过内存屏障保证共享变量的可见性与有序性,但不保证原子性。本文深入解析其原理、典型应用(如DCL单例、状态标记)及与synchronized、原子类的区别,助你正确高效使用。
157 12
|
1月前
|
安全 Java
深入拆解 ReentrantLock:从底层实现到生产最佳实践
本文深入剖析ReentrantLock底层原理,基于AQS框架详解state状态、CLH队列及公平/非公平锁机制;对比synchronized在实现、功能(可中断、多条件变量)和性能上的差异;结合代码演示三类锁适用场景与最佳实践,助你写出高效、健壮的并发程序。
298 4
|
消息中间件 Java API
RocketMQ事务消息, 图文、源码学习探究~
介绍 RocketMQ是阿里巴巴开源的分布式消息中间件,它是一个高性能、低延迟、可靠的消息队列系统,用于在分布式系统中进行异步通信。 从4.3.0版本开始正式支持分布式事务消息~ RocketMq事务消息支持最终一致性:在普通消息基础上,支持二阶段的提交能力。将二阶段提交和本地事务绑定,实现全局提交结果的一致性。 原理、流程 本质上RocketMq的事务能力是基于二阶段提交来实现的 在消息发送上,将二阶段提交与本地事务绑定 本地事务执行成功,则事务消息成功,可以交由Consumer消费 本地事务执行失败,则事务消息失败,Consumer无法消费 但是,RocketMq只能保证本地事务
|
9月前
|
网络安全 云计算
如何设置阿里云轻量应用服务器镜像?
本文介绍了在阿里云轻量应用服务器上创建与配置镜像的详细步骤。镜像是一种特殊的文件系统映射,可用于快速克隆服务器配置。内容涵盖准备条件、登录控制台、创建实例、生成镜像、下载与设置镜像,以及如何使用镜像启动新实例。适合希望提升服务器部署效率的用户参考。
|
1月前
|
监控 Java 数据库连接
Java 线程池核心参数设计与生产环境调优实战
本文系统解析Java线程池核心原理:详解七大参数(corePoolSize、maximumPoolSize等)、执行流程、队列类型选择及拒绝策略;深入讲解生产环境动态调优方法,含Spring Boot实战代码;并提供线程泄露与任务堆积的排查思路、工具及解决方案。
252 3
|
5月前
|
监控 Java 测试技术
微服务保护Sentinel
本课程深入讲解微服务中雪崩问题的成因及解决方案,重点介绍阿里开源流量治理组件Sentinel的应用。涵盖Sentinel的部署与整合、限流模式(直接、关联、链路)、流控效果(快速失败、预热、排队等待)、熔断降级、线程隔离、授权规则及规则持久化等内容,结合Jmeter压测实战,帮助开发者全面提升微服务稳定性与高可用能力。
|
7月前
|
存储 人工智能 前端开发
超越问答:深入理解并构建自主决策的AI智能体(Agent)
如果说RAG让LLM学会了“开卷考试”,那么AI智能体(Agent)则赋予了LLM“手和脚”,使其能够思考、规划并与真实世界互动。本文将深入剖析Agent的核心架构,讲解ReAct等关键工作机制,并带你一步步构建一个能够调用外部工具(API)的自定义Agent,开启LLM自主解决复杂任务的新篇章。
1565 6
|
自然语言处理 IDE 开发工具
5分钟完成手势识别项目!CodeBuddy的Craft模式让传统编程方法沦为古董?
本文介绍了使用CodeBuddy快速开发手势识别程序的方法。首先安装Python 3.9.13并配置VS Code环境,接着通过pip安装依赖库`mediapipe`和`opencv-python`。利用CodeBuddy的Craft模式,仅需输入自然语言描述即可生成基础代码,经过简单调整后即可运行。代码实现了四种手势识别(OK、竖大拇指、握拳、张开手掌),并通过摄像头实时展示结果。尽管电脑摄像头像素较低,但识别效果良好。本文旨在帮助读者了解CodeBuddy的强大功能,并激发更多创意应用。
926 20
|
设计模式 前端开发 Java
SpringMVC基础入门及工作流程---全方面详细介绍
SpringMVC基础入门及工作流程---全方面详细介绍
746 0

热门文章

最新文章