Java 多线程三大特性详解:原子性、可见性、有序性

——含完整代码示例与底层原理剖析

文档目标:系统掌握 Java 并发三大核心特性的实现机制、使用场景及底层原理

一、引言:为什么需要三大特性?

在多线程环境中,多个线程共享内存,若不加控制,会出现以下问题:

  • 原子性破坏i++ 操作被拆分为“读-改-写”,导致结果错误;
  • 可见性缺失:线程 A 修改变量,线程 B 仍读取旧值;
  • 有序性混乱:编译器/CPU 指令重排序导致逻辑错乱(如 DCL 单例失效)。

Java 内存模型(JMM)通过 原子性(Atomicity)、可见性(Visibility)、有序性(Ordering) 三大特性保障并发安全。

⚠️ 注意:“一致性”不是 Java 并发三大特性的标准术语,常见于数据库(ACID)或分布式系统(CAP)。

二、三大特性定义

特性定义核心问题
原子性操作不可分割,要么全部执行成功,要么完全不执行复合操作被线程交错打断
可见性一个线程修改共享变量后,其他线程能立即看到最新值工作内存与主内存不同步
有序性程序执行顺序应符合代码编写顺序编译器/CPU 指令重排序

三、原子性(Atomicity)

1. synchronized —— JVM 内置锁

✅ 代码示例
publicclassSynchronizedExample{privateint count =0;publicsynchronizedvoidincrement(){ count++;// 非原子操作,需同步保护}publicstaticvoidmain(String[] args)throwsInterruptedException{SynchronizedExample e =newSynchronizedExample();Thread t1 =newThread(()->{for(int i =0; i <1000; i++) e.increment();});Thread t2 =newThread(()->{for(int i =0; i <1000; i++) e.increment();}); t1.start(); t2.start(); t1.join(); t2.join();System.out.println("Final count: "+ e.count);// 输出 2000}}
🔍 底层原理
  • Monitor 机制:每个 Java 对象关联一个 Monitor(C++ 实现),由 JVM 管理。
  • 对象头 Mark Word:存储锁状态(无锁/偏向锁/轻量级锁/重量级锁)。
  • 字节码指令 :
    • monitorenter:进入同步块时获取锁;
    • monitorexit:退出时释放锁。
  • 内存语义:进入/退出同步块时自动刷新工作内存 ↔ 主内存,同时保证可见性与有序性

锁升级路径 :

无锁 → 偏向锁(单线程优化) → 轻量级锁(自旋) → 重量级锁(OS 阻塞) 

2. ReentrantLock —— 显式可重入锁

✅ 代码示例
importjava.util.concurrent.locks.ReentrantLock;publicclassReentrantLockExample{privateint count =0;privatefinalReentrantLock lock =newReentrantLock();publicvoidincrement(){ lock.lock();try{ count++;}finally{ lock.unlock();// 必须在 finally 中释放}}publicstaticvoidmain(String[] args)throwsInterruptedException{ReentrantLockExample e =newReentrantLockExample();Thread t1 =newThread(()->{for(int i =0; i <1000; i++) e.increment();});Thread t2 =newThread(()->{for(int i =0; i <1000; i++) e.increment();}); t1.start(); t2.start(); t1.join(); t2.join();System.out.println("Final count: "+ e.count);// 输出 2000}}
🔍 底层原理
  • AQS(AbstractQueuedSynchronizer) :
    • 核心字段:volatile int state(锁状态)、Node head/tail(CLH 双向等待队列)。
  • 加锁流程:
    1. CAS 尝试将 state 从 0 → 1;
    2. 失败则封装线程为 Node 入队,并调用 LockSupport.park() 阻塞;
  • 解锁流程 :
    1. CAS 将 state 减 1;
    2. 唤醒队列头节点(LockSupport.unpark())。
  • 可重入性:记录当前持有线程,同一线程可多次加锁(state 递增)。
  • 优势:支持公平锁、超时、可中断、多条件变量(Condition)。

3. 原子类(如 AtomicInteger

✅ 代码示例
importjava.util.concurrent.atomic.AtomicInteger;publicclassAtomicExample{privateAtomicInteger count =newAtomicInteger(0);publicvoidincrement(){ count.incrementAndGet();// 原子自增}publicstaticvoidmain(String[] args)throwsInterruptedException{AtomicExample e =newAtomicExample();Thread t1 =newThread(()->{for(int i =0; i <1000; i++) e.increment();});Thread t2 =newThread(()->{for(int i =0; i <1000; i++) e.increment();}); t1.start(); t2.start(); t1.join(); t2.join();System.out.println("Final count: "+ e.count.get());// 输出 2000}}
🔍 底层原理
  • Unsafe 类:调用 native 方法 compareAndSwapInt(),利用 CPU 原子指令(如 x86 的 LOCK CMPXCHG)。
  • 无锁并发:失败则自旋重试,避免线程阻塞开销。
  • volatile 修饰:内部字段 private volatile int value天然具备可见性与部分有序性
  • ABA 问题:可通过 AtomicStampedReference 解决。

CAS(Compare-And-Swap) :

publicfinalintincrementAndGet(){return unsafe.getAndAddInt(this, valueOffset,1)+1;}

4. StampedLock —— 乐观读写锁

✅ 代码示例
importjava.util.concurrent.locks.StampedLock;publicclassStampedLockExample{privatedouble x =0, y =0;privatefinalStampedLock sl =newStampedLock();voidmove(double dx,double dy){long stamp = sl.writeLock();try{ x += dx; y += dy;}finally{ sl.unlockWrite(stamp);}}doubledistanceFromOrigin(){long stamp = sl.tryOptimisticRead();// 乐观读double currentX = x, currentY = y;if(!sl.validate(stamp)){// 检查是否被写入 stamp = sl.readLock();// 升级为悲观读try{ currentX = x; currentY = y;}finally{ sl.unlockRead(stamp);}}returnMath.sqrt(currentX * currentX + currentY * currentY);}publicstaticvoidmain(String[] args){StampedLockExample obj =newStampedLockExample(); obj.move(3,4);System.out.println("Distance: "+ obj.distanceFromOrigin());// 输出 5.0}}
🔍 底层原理
  • 改进型 AQS:使用 long stamp 代替 int state,低 7 位表示锁模式,高位为版本号。
  • 乐观读 :
    • 不加锁,仅记录版本号;
    • 读取后验证版本是否变化(validate(stamp));
    • 若未被写入,则读成功;否则升级为悲观读。
  • 写锁与读锁互斥,但多个乐观读可并发。
  • 不支持重入,且无条件变量。
  • 适用场景:读远多于写的高性能计算(如几何坐标计算)。

四、可见性(Visibility)

1. volatile —— 轻量级可见性保障

✅ 代码示例
publicclassVolatileVisibilityExample{privatevolatileboolean running =true;// volatile 保证可见性publicvoidstop(){ running =false;}publicvoidrunTask(){while(running){System.out.println("Working...");try{Thread.sleep(500);}catch(InterruptedException e){break;}}System.out.println("Task stopped.");}publicstaticvoidmain(String[] args)throwsInterruptedException{VolatileVisibilityExample task =newVolatileVisibilityExample();Thread worker =newThread(task::runTask); worker.start();Thread.sleep(2000); task.stop();// 主线程停止任务 worker.join();}}
❗ 若去掉 volatileworker 线程可能永远看不到 running = false,导致死循环。
🔍 底层原理
  • JMM 内存语义:
    • 写 volatile:立即刷回主内存,并使其他 CPU 缓存行失效;
    • 读 volatile:从主内存重新加载最新值。
  • 内存屏障(Memory Barrier):
    • 写屏障StoreStore + StoreLoad,禁止写之前的操作重排到写之后;
    • 读屏障LoadLoad + LoadStore,禁止读之后的操作重排到读之前。
  • 缓存一致性协议:如 MESI 协议,确保多核 CPU 缓存一致性。
  • 不保证原子性:如 i++ 仍需锁或原子类。

2. synchronized 保证可见性

✅ 代码示例(复用前文 SynchronizedExample
🔍 底层原理
  • happens-before 规则:解锁 happens-before 后续加锁。
  • 内存同步:
    • 进入同步块:清空工作内存,从主内存重新加载共享变量;
    • 退出同步块:将修改后的共享变量 flush 到主内存。
  • 底层通过 Monitor 的 entry/exit 操作触发内存同步。

3. final 字段的可见性

✅ 代码示例
publicclassFinalFieldExample{privatefinalint x;privatefinalint y;publicFinalFieldExample(int x,int y){this.x = x;this.y = y;// 构造完成后对所有线程可见}publicintsum(){return x + y;// 无需同步,安全}publicstaticvoidmain(String[] args){FinalFieldExample obj =newFinalFieldExample(3,4);newThread(()->{System.out.println("Sum in thread: "+ obj.sum());// 输出 7}).start();}}
🔍 底层原理
  • JLS §17.5 final 语义:
    • 构造函数内对 final 字段的写操作,不会被重排到构造函数之外
    • 构造完成后,final 字段的值对所有线程立即可见
  • 安全发布:只要对象正确构造(未逸出),final 字段无需同步即可安全读取。
  • 适用场景:构建不可变对象(Immutable Object)。

五、有序性(Ordering)

1. volatile 禁止重排序(DCL 单例)

✅ 代码示例
publicclassDoubleCheckedLockingSingleton{// 必须加 volatile!否则可能返回未完全初始化的对象privatevolatilestaticDoubleCheckedLockingSingleton instance;privateDoubleCheckedLockingSingleton(){}publicstaticDoubleCheckedLockingSingletongetInstance(){if(instance ==null){synchronized(DoubleCheckedLockingSingleton.class){if(instance ==null){ instance =newDoubleCheckedLockingSingleton();// 关键:防止重排序}}}return instance;}publicvoiddoSomething(){System.out.println("Singleton working...");}publicstaticvoidmain(String[] args){DoubleCheckedLockingSingleton s1 =getInstance();DoubleCheckedLockingSingleton s2 =getInstance();System.out.println(s1 == s2);// true s1.doSomething();}}
🔍 底层原理
  • 对象创建三步骤:
    1. 分配内存空间;
    2. 初始化对象;
    3. instance 指向内存地址。
  • 重排序风险:步骤 2 与 3 可能重排为 1 → 3 → 2,导致其他线程拿到未初始化对象。
  • volatile 作用:
    • instance = new ... 写操作后插入 StoreStore 屏障
    • 确保初始化完成后再赋值引用。
  • happens-before:volatile 写 happens-before 后续 volatile 读。

2. synchronized 保证有序性

✅ 代码示例(复用前文)
🔍 底层原理
  • 程序顺序规则:同步块内部代码按程序顺序执行。
  • 监视器锁规则:解锁 happens-before 后续加锁,建立跨线程顺序。
  • 底层 Monitor 机制隐含内存屏障,禁止临界区内外的操作重排。

六、综合对比表

方案原子性可见性有序性底层机制适用场景
synchronizedMonitor(对象头 Mark Word)通用同步,复合操作
ReentrantLockAQS + CAS + CLH 队列需要高级控制(超时、公平等)
原子类✅(单变量)CAS + volatile + Unsafe单变量无锁并发
StampedLock改进 AQS + 乐观读读多写少高性能场景
volatile✅(部分)内存屏障 + 缓存一致性协议状态标志、DCL 单例
final✅(构造后)✅(构造阶段)JMM final 语义不可变对象

七、最佳实践建议

  • 状态标志volatile boolean flag
  • 计数器/累加器AtomicIntegerLongAdder(高并发分段累加)
  • 复合操作(如转账)synchronized(简单)或 ReentrantLock(灵活)
  • 高性能读多写少StampedLock
  • 不可变对象final 字段 + 构造函数安全初始化
  • 避免过度同步:锁粒度越小越好,优先无锁方案

八、附录:关键概念速查

概念说明
JMM(Java Memory Model)定义线程与主内存交互规则
happens-beforeJMM 核心规则,保证操作可见性与有序性
CAS(Compare-And-Swap)CPU 原子指令,无锁并发基础
AQSJUC 锁框架基石,基于 CLH 队列
内存屏障禁止指令重排序的硬件/编译器指令
💡 口诀记忆原(原子性)可(可见性)有(有序性)——Java 并发三基石。

文档版本:v1.0
最后更新:2026年1月19日
作者:chen

Read more

PyTorch实战——基于文本引导的图像生成技术与Stable Diffusion实践

PyTorch实战——基于文本引导的图像生成技术与Stable Diffusion实践

PyTorch实战——基于文本引导的图像生成技术与Stable Diffusion实践 * 0. 前言 * 1. 基于扩散模型的文本生成图像 * 2. 将文本输入编码为嵌入向量 * 3. 条件 UNet 模型中的文本数据融合机制 * 4. 使用 Stable Diffusion 模型生成图像 * 相关链接 0. 前言 在本节中,我们将为扩散模型添加文本控制能力。学习如何通过文字描述来引导图像生成过程,实现从"纯噪声+文本"生成图像,而不仅是从纯噪声生成。 1. 基于扩散模型的文本生成图像 在扩散模型的 UNet 模型训练流程中,我们仅训练模型从含噪图像中预测噪声。为实现文生图功能,需使用以下架构,将文本作为额外输入注入 UNet 模型: 这样的 UNet 模型称为条件 UNet 模型 ,或者更精确地说,是文本条件 UNet

By Ne0inhk
用OpenClaw做飞书ai办公机器人(含本地ollama模型接入+自动安装skills+数据可视化)

用OpenClaw做飞书ai办公机器人(含本地ollama模型接入+自动安装skills+数据可视化)

执行git clone https://github.com/openclaw/openclaw克隆项目,执行cd openclaw进入项目 执行node --version看看node的版本是否大于等于22(没有node.js需自行安装),再执行npm install -g pnpm安装作为包管理器,并执行pnpm install安装依赖 首次执行pnpm ui:build构建 Web UI(会先安装 ui/ 目录的依赖) 执行pnpm build构建主程序 执行pnpm openclaw onboard --install-daemon运行配置向导(安装守护进程),完成初始化 按键盘右箭头选择Yes,同样Yes 任选一个模型提供商都行,没有对应的提供商的密钥可以跳过,如果是本地模型选vLLM(需用vLLM框架启动模型,有性能优势,但原生vLLM仅完全支持Linux的cuda)、Custom Provider(可以连接任何 OpenAI 或 Anthropic 兼容的端点,

By Ne0inhk

雷达信号处理中的CFAR技术详解

好的,我来为您总结归纳雷达信号处理中的恒虚警(CFAR)技术,并提供一个基于MATLAB的实际用例。 🧐 雷达信号处理之恒虚警(CFAR) 恒虚警率(Constant False Alarm Rate, CFAR)是一种自适应阈值目标检测技术,在雷达信号处理中用于从噪声和杂波背景中检测出目标回波。其核心思想是:无论背景噪声或杂波的功率如何变化,都保持虚警概率( )为一个预先设定的常数。 🎯 1. 基本原理与流程 CFAR算法通过实时估计待检测单元(Cell Under Test, CUT)周围的背景噪声或杂波功率,并根据期望的虚警率 自适应地确定检测阈值 。 主要步骤: 1. 滑动窗口(Detection Window):在待检测数据(通常是距离-多普勒图或距离向数据)上设定一个固定大小的滑动窗口。 2. 单元划分:窗口内的单元被划分为三个部分: * 待检测单元(CUT):位于窗口中心,是我们要判断是否包含目标的单元。 如果 ,则判断不存在目标(No Target)。 如果 ,则判断存在目标(

By Ne0inhk
15-OpenClaw与Telegram机器人集成

15-OpenClaw与Telegram机器人集成

OpenClaw 与 Telegram 机器人集成 ✦ 免费专栏|全套教程: OpenClaw 从入门到精通 ✦ 开篇总览|最新目录: 最新 OpenClaw 教程|从入门到精通|AI 智能助手 / 自动化 / Skills 实战(原 Clawdbot/Moltbot) 概述 OpenClaw 提供了强大的 Telegram Bot 集成能力,通过统一的 message 工具接口,可以轻松实现消息收发、群组管理、媒体处理等功能。本案例将详细介绍如何通过 OpenClaw 构建功能完整的 Telegram Bot。 目录 * 前置准备 * Bot 创建 * Webhook 配置 * 消息处理 * 命令设计 * 高级功能 * 最佳实践 前置准备

By Ne0inhk