什么是 Java 中的原子性、可见性和有序性?

什么是 Java 中的原子性、可见性和有序性?

👨‍⚕️主页: gis分享者
👨‍⚕️感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅!
👨‍⚕️收录于专栏:java 200道热门面试题


文章目录


一、🍀回答重点

原子性、可见性、有序性是 Java 并发编程的三大核心特性,任何并发 bug 基本都能归到这三类里面。

在这里插入图片描述

1 ☘️原子性

原子性指一个操作要么全部执行完,要么压根没执行,中间不会被其他线程打断。比如 i++ 这个操作看着像一行代码,实际上是读取、加 1、写回三个步骤,多线程环境下就可能出问题。

2 ☘️可见性

可见性指一个线程修改了共享变量,其他线程能立刻看到最新值。CPU 有自己的高速缓存,线程修改的值可能还躺在缓存里没刷回主内存,别的线程就读到了旧值。

3 ☘️有序性

有序性指程序执行顺序和代码写的顺序一致。编译器和 CPU 为了性能会对指令做重排序,单线程下没问题,多线程就可能出现诡异的 bug。


下面用一个经典的例子演示这三个问题是怎么搞出 bug 的:

publicclassSingleton{privatestaticSingleton instance;publicstaticSingletongetInstance(){if(instance ==null){// 第一次检查synchronized(Singleton.class){if(instance ==null){// 第二次检查 instance =newSingleton();// 问题就出在这}}}return instance;}}

这段双重检查锁定看起来没毛病,但 instance = new Singleton() 这行代码实际上分三步:分配内存空间、初始化对象、把引用指向内存地址。CPU 可能把第 2 步和第 3 步重排序,导致另一个线程拿到一个还没初始化完的对象,直接空指针。解决办法就是给 instance 加上 volatile。

二、🍀扩展知识

1. ☘️原子性的保障手段

Java 里保证原子性主要靠两种方式:锁和 CAS。

synchronized 和 Lock 是最直接的手段,进入临界区的线程独占资源,其他线程只能干等着。但锁的开销不小,线程切换、阻塞唤醒都是重量级操作。

CAS 是一种乐观锁思路,底层依赖 CPU 的 cmpxchg 指令。比如 AtomicInteger 的 incrementAndGet,它会不断尝试"比较当前值是否等于预期值,等于就更新",失败就重试。CAS 避免了线程阻塞,但在竞争激烈时会疯狂自旋,CPU 空转。

// AtomicInteger 的自增底层就是 CASAtomicInteger count =newAtomicInteger(0); count.incrementAndGet();// 内部循环 CAS 直到成功

JDK 8 引入了 LongAdder,思路是把一个变量拆成多个 Cell,不同线程操作不同的 Cell,最后汇总。高并发场景下比 AtomicLong 快很多,Elasticsearch 的计数器就用的这种方案。

在这里插入图片描述

2. ☘️可见性的底层原理

可见性问题的根源在于 CPU 缓存。现代 CPU 都有 L1、L2、L3 多级缓存,每个核心有自己的 L1/L2,线程读写变量时优先操作缓存。如果线程 A 在 CPU0 上改了变量,值还在 L1 缓存里,线程 B 在 CPU1 上读的还是旧值。

volatile 的作用就是强制刷新缓存。写 volatile 变量时,JVM 会插入 StoreStore 屏障和 StoreLoad 屏障,把缓存里的数据刷回主内存;读 volatile 变量时,会插入 LoadLoad 屏障和 LoadStore 屏障,强制从主内存读取。

synchronized 也能保证可见性。线程退出 synchronized 块时,会把所有修改刷回主内存;进入 synchronized 块时,会清空本地缓存,强制从主内存重新加载。所以 synchronized 块里的代码不需要额外加 volatile。

final 字段也有可见性保证。JVM 保证对象构造完成后,final 字段的值对其他线程可见,不需要额外同步。这就是为什么 String 的 value 数组是 final 的。

3. ☘️有序性与指令重排

编译器和 CPU 都会做指令重排序,目的是充分利用 CPU 流水线,提高执行效率。

编译器重排是在生成字节码或机器码时调整指令顺序。比如两条不相关的赋值语句,编译器可能调换顺序来优化寄存器使用。

CPU 重排更常见,现代 CPU 都是乱序执行。CPU 会把没有数据依赖的指令并行执行,执行完再按原始顺序提交结果。单线程下完全没问题,因为 CPU 保证了 as-if-serial 语义,执行结果和顺序执行一样。

但多线程环境下,A 线程的两条指令对 A 来说没依赖,对 B 线程可能就有依赖。经典的例子是上面的双重检查锁定,对象初始化和引用赋值对构造线程没依赖,但其他线程可能在初始化完成前就拿到了引用。

JMM 定义了 happens-before 规则来约束重排序。只要操作 A happens-before 操作 B,那 A 的结果对 B 一定可见,A 的执行顺序也一定在 B 之前。

4. ☘️三大特性的实现方式对比

优缺点对比

特性volatilesynchronizedLockAtomic
原子性不保证保证保证保证
可见性保证保证保证保证
有序性禁止重排序临界区内有序临界区内有序单个操作有序
性能最轻中等可控较轻
适用场景状态标记临界区保护需要精细控制计数器

三、🍀面试官追问

提问:volatile 能保证原子性吗?为什么 volatile int count 的 count++ 不是线程安全的??
回答:volatile 只保证可见性和禁止重排序,不保证原子性。count++ 实际上是读取、加 1、写回三个步骤,多个线程可能同时读到同一个值,各自加 1 后写回,结果就少加了。想要原子自增得用 AtomicInteger 或者 synchronized。

提问:synchronized 和 volatile 在底层实现上有什么区别?

回答:volatile 是通过内存屏障实现的,写操作插入 StoreStore 和 StoreLoad 屏障,读操作插入 LoadLoad 和 LoadStore 屏障,纯粹靠 CPU 指令保证,不涉及锁。synchronized 底层是 monitor 机制,JVM 会在对象头里记录锁状态,涉及到偏向锁、轻量级锁、重量级锁的升级过程,重量级锁要靠操作系统的互斥量,有线程切换开销。

提问:为什么双重检查锁定的单例需要加 volatile,不加会出什么问题?
回答:new 对象分三步:分配内存、初始化、引用赋值。不加 volatile 的话,CPU 可能把初始化和引用赋值重排序,另一个线程可能在第一次 null 检查时拿到一个非 null 但还没初始化完的引用,直接用就空指针或者数据错乱。volatile 禁止了这种重排序。

Read more

用 Spring Boot + Three.js + Vue3 构建简单的仓库数字孪生系统

用 Spring Boot + Three.js + Vue3 构建简单的仓库数字孪生系统

🧭 本文属于专栏《Java × 工业智能》第 12 篇 | GitHub 源码:github.com/iweidujiang/java-industrial-smart 在工业场景中,仓库环境的监控是确保存储安全的关键: * 粮食仓库:如何实时监控不同仓的温湿度变化? * 药品仓库:如何确保存储环境符合标准? * 物流仓库:如何快速了解各区域的环境状态? 本篇将用 Spring Boot + Three.js 实现一个简单的仓库数字孪生系统,模拟实际工业智能场景的解决方案,实现: * 仓库鸟瞰图的 3D 可视化 * 点击仓库显示实时温湿度信息 * 基于 WebSocket 的数据实时传输 * 支持场景旋转查看 一、简单介绍 1、核心特征 特征说明反例直观可视化用 3D 场景直观展示物理空间仅数字表格,无空间展示实时数据物理环境数据与数字镜像实时同步静态展示,无数据更新交互性支持用户与数字孪生的简单交互(如点击查看)仅被动展示,无用户交互易于部署技术栈简单,部署便捷复杂依赖,部署困难

By Ne0inhk
FARS全自动科研系统技术深度解析:从多智能体架构到工业化科研范式

FARS全自动科研系统技术深度解析:从多智能体架构到工业化科研范式

前言 2026年2月12日至2月22日,一场持续228小时33分钟的直播在全球AI社区引发了持续震荡。屏幕另一端,一个名为FARS(Fully Automated Research System)的全自动研究系统,在没有人类干预的情况下,自主完成了从文献调研到论文撰写的完整科研流程,最终产出100篇学术论文,总消耗114亿Token,成本10.4万美元。 这场实验的意义远不止于“AI写论文”的简单升级。它向世界展示了科学发现的根本范式正在发生转移——从依赖人类灵感的“手工作坊”,转向由AI驱动的“工业化流水线”。本文将从最底层的技术细节出发,逐层拆解FARS的系统架构、智能体协作机制、资源调度策略、成本控制模型,以及与竞品的技术对比,为读者呈现一个完整的全自动科研系统技术图谱。 第一章 系统总体架构:四智能体流水线设计 1.1 核心设计理念:研究系统的第一性原理 FARS的设计并非简单地模仿人类科研流程,而是基于团队对“研究系统”本质的重新思考。创始团队提出,一个理想的研究系统应遵循两条基本原则: 1. 高效拓展知识边界:系统的吞吐量应成为核心评估指标,而非单篇论文的完

By Ne0inhk
微服务架构下Spring Session与Redis分布式会话实战全解析

微服务架构下Spring Session与Redis分布式会话实战全解析

目录 🚀摘要 📖 为什么需要分布式会话管理? 🎯 传统会话管理的痛点 🔄 分布式会话的演进历程 🏗️ Spring Session架构深度解析 🎨 设计理念:透明化抽象层 🔧 核心组件解析 1. SessionRepositoryFilter - 入口拦截器 2. RedisSessionRepository - Redis实现 📊 性能特性分析 🔧 实战:从零构建企业级分布式会话系统 🛠️ 环境准备与项目搭建 Maven依赖配置 Redis集群配置 🎯 Spring Session高级配置 自定义序列化策略 会话事件监听器 💻 完整代码示例:电商购物车会话管理 📈 性能优化实战 1. Redis数据结构优化 2. 会话数据分片策略 🏢 企业级高级应用 📊 大型电商平台案例:双11大促会话管理 分级存储策略 会话迁移服务 ⚡ 性能优化技巧 1. 读写分离优化 2. 本地缓存优化 🔍 故障排查指南 1. 常见问题与解决方案 2. 监控指标配置

By Ne0inhk
【Web APIs】移动端常用的 JavaScript 开发插件 ⑤ ( JavaScript 插件使用流程 | iScroll 插件案例 )

【Web APIs】移动端常用的 JavaScript 开发插件 ⑤ ( JavaScript 插件使用流程 | iScroll 插件案例 )

文章目录 * 一、JavaScript 插件使用流程 * 二、iScroll 插件 * 1、iScroll 插件简介 * 2、代码示例 - iScroll 插件 * 3、执行效果 一、JavaScript 插件使用流程 在之前的博客中 , 介绍了 Swiper 插件 及其用法 , 可以总结出 JS 插件的使用流程如下 ; JavaScript 插件使用流程 : * 步骤一 : 确认插件功能 ,查看都能实现什么效果, 功能如何 ; * 步骤二 : 官网查看文档 , 到 JS 插件官网查询 插件的使用明说 ; * 步骤三 : 下载插件 ,将插件下载到本地; * 步骤四 : 分析案例 , 在下载的插件中一般都提供 demo 案例 ,分析对应案例效果 , 以及查找需要导入的依赖文件,

By Ne0inhk