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

并发 - AQS 与 Volatile

知识点 5.5:并发编程基石 —— AQS 与 Volatile

在深入了解各种锁和同步工具之前,必须先理解 JUC 框架的两个核心基石:volatile 关键字和 AQS 框架。


1. volatile 关键字:并发编程的“信号旗”

volatile 是一个 Java 变量修饰符,它非常轻量,但提供了两个至关重要的保证。

1.1 保证可见性 (Visibility)

  • 问题所在: 在多核 CPU 架构下,每个 CPU 都有自己的高速缓存(Cache)。当一个线程(运行在 CPU-1 上)修改了一个共享变量的值,它可能只是更新了自己的缓存,而没有立刻写回主内存。此时,另一个线程(运行在 CPU-2 上)读取这个变量时,可能会从自己的缓存里读到一个过时的、旧的值,导致数据不一致。

  • volatile 的作用: 当一个变量被声明为 volatile 后,JVM 会保证:

    1. 写操作:对这个变量的修改会立即被刷新到主内存中。
    2. 读操作:每次读取这个变量,都会强制从主内存中重新加载,而不是使用线程本地的缓存。
  • 生活比喻: volatile 变量就像一个公共的、唯一的电子公告牌。当一个线程更新了公告牌上的内容,其他所有线程都能立刻看到最新的内容,而不是看自己手里抄录的、可能已经过时的小纸条。

1.2 禁止指令重排序 (Instruction Reordering)

  • 问题所在: 为了提高性能,编译器和 CPU 可能会在不影响单线程最终结果的前提下,打乱代码的执行顺序。但在多线程环境下,这种重排序可能会导致意想不到的错误(例如著名的“双重检查锁定”单例模式失效问题)。

  • volatile 的作用: volatile 关键字会作为一个“内存屏障”(Memory Barrier)。当程序执行到 volatile 变量的读或写操作时,它前面的所有操作都必须已经完成,且结果对后续操作可见;它后面的所有操作也必须在它之后才能开始。这有效地阻止了指令重排序跨越这个屏障。

1.3 volatile 不能保证原子性

这是 volatile 最重要的一个局限性,也是面试高频陷阱。

  • 原子性: 指一个操作是不可分割、不可中断的。要么完全执行成功,要么完全不执行。

  • count++ 的问题: 这个操作看似一步,实际包含三步:“1. 读取 count 的值;2. 将值加 1;3. 将新值写回 count”。volatile 无法保证这三步作为一个整体的原子性。

演示代码:VolatileNoAtomicDemo.java

以下代码清晰地演示了 volatile 无法保证复合操作(如 ++)的原子性。期望结果是 100,000,但实际运行结果通常会小于此值,证明有自增操作丢失。


package com.study.concurrency;import java.util.concurrent.atomic.AtomicInteger; // 引入 AtomicInteger/*** 演示 volatile 不保证原子性的经典例子:多线程计数器* 运行结果:最终 count < 期望结果,证明 volatile 无法保证“++”操作的原子性*/
public class VolatileNoAtomicDemo {// 原始问题:使用 volatile 修饰共享变量,但 ++ 操作非原子private static volatile int count = 0; // 此处用 volatile 无法解决原子性问题// 用于解决原子性问题的方案一:使用 AtomicInteger// private static AtomicInteger atomicCount = new AtomicInteger(0);// 用于解决原子性问题的方案二:使用 synchronized 块// private static final Object lock = new Object();private static final int THREADS = 10;private static final int INCREMENTS_PER_THREAD = 10_000;public static void main(String[] args) throws InterruptedException {Thread[] threads = new Thread[THREADS];for (int i = 0; i < THREADS; i++) {threads[i] = new Thread(() -> {for (int j = 0; j < INCREMENTS_PER_THREAD; j++) {// 原始问题代码:count++; // 非原子操作:读-改-写三步// 方案一修正:使用 AtomicInteger// atomicCount.incrementAndGet(); // 原子操作// 方案二修正:使用 synchronized// synchronized (lock) { // 或者 synchronized (VolatileNoAtomicDemo.class)//     count++;// }}});threads[i].start();}for (Thread t : threads) {t.join();}System.out.println("期望结果 = " + (THREADS * INCREMENTS_PER_THREAD));// System.out.println("实际结果 (volatile) = " + count); // 如果使用 volatile int countSystem.out.println("实际结果 = " + count); // 或者使用 atomicCount.get()}
}

问题分析:为什么 volatile 会失效?

  • volatile 只能保证可见性有序性,即保证读取到 count 的值是最新的,并且阻止了指令重排序。

  • 但是,count++ 并非一个单一的原子操作。当多个线程同时进行“读取-修改-写入”这三个步骤时,即使它们都读取到了最新的值,也可能在修改和写入的间隙被其他线程抢占 CPU,导致更新丢失。例如:

    1. 线程A 读取 count (值为 100)。
    2. 线程B 读取 count (此时 count 仍是 100,因为线程A还没写回)。
    3. 线程A 将 count 计算为 101,并写入主内存。
    4. 线程B 将 count 计算为 101,并写入主内存。
    • 结果:count 最终是 101,而不是预期的 102。一个自增操作丢失。

如何修正这个问题来保证线程安全?
要保证 count++ 这样的复合操作的原子性,必须使用更强大的同步机制:

  1. 使用 synchronized 关键字

    • count++ 操作放入 synchronized 代码块中。synchronized 保证了同一时间只有一个线程可以进入该代码块,从而使整个“读-改-写”序列变为原子操作。
    // 示例:
    // synchronized (VolatileNoAtomicDemo.class) { // 或 synchronized (任意共享对象)
    //     count++;
    // }
    
    • synchronized 能够保证原子性、可见性、有序性。但它是一个重量级锁,在高并发竞争时性能开销较大。
  2. 使用 java.util.concurrent.atomic 包下的原子类 (推荐)

    • 对于简单的原子操作(如整数自增、布尔值设置等),Java 提供了 AtomicInteger, AtomicLong, AtomicBoolean 等原子类。
    • 这些类内部通过CAS (Compare-And-Swap) 等无锁机制来保证操作的原子性,性能通常比 synchronized 更好。
    // 示例:
    // private static AtomicInteger count = new AtomicInteger(0);
    // ...
    // count.incrementAndGet(); // 原子性地执行 count++
    
    • 结论volatile 适用于一写多读的场景,或者当变量的更新不依赖于其当前值时。对于 count++ 这样的复合操作,必须使用 synchronizedAtomicInteger 这样的原子类来保证线程安全。

2. AQS (AbstractQueuedSynchronizer):JUC 的龙骨

AQS 是 JUC 中绝大部分锁和同步工具的核心实现框架。它是一个抽象类,本身不是一个锁,而是用来构建锁和同步器的基础骨架。

2.1 AQS 的核心设计思想

AQS的核心思想是一种模板方法模式的应用。它提供了一个同步状态管理state)和线程排队(FIFO队列)的基础框架。开发者只需实现对state的获取和释放逻辑,而线程的排队、阻塞、唤醒等复杂操作都由AQS框架本身来完成。

2.2 两大核心数据结构

  1. volatile int state: 一个用 volatile 修饰的整型变量,用于表示同步状态

    • state 的含义由具体实现类来定义。例如,在ReentrantLock中,它表示锁的重入次数;在Semaphore中,它表示剩余的许可数量。
    • AQS通过getState(), setState(), compareAndSetState()这三个方法来原子地操作这个状态。
  2. 一个 FIFO 的线程等待队列 (CLH 队列):

    • 这是一个双向链表,用于存放所有等待获取同步状态的线程。
    • 当一个线程获取锁失败后,它就会被封装成一个Node节点,加入到这个队列的尾部并被挂起,等待被唤醒。

2.3 底层实现:线程如何排队与唤醒?

这是AQS的精髓所在,也是面试深度考察点。

  1. 线程安全入队

    • 当一个新线程获取锁失败需要入队时,它会被封装成一个Node节点。
    • AQS通过自旋(for循环)+ CAS(Compare-And-Swap)这种无锁的方式,来原子性地将新节点添加到等待队列的队尾。这避免了在入队这个高并发操作上加锁,提高了效率。
  2. 线程挂起(阻塞)

    • 线程成功入队后,AQS会调用 LockSupport.park(this) 方法将当前线程挂起。
    • LockSupport是JUC包中的一个工具类,它提供的park()/unpark()相比于Object.wait()/notify(),优点在于无需获取对象的监视器锁,并且可以unparkpark而不会丢失信号。
  3. 线程唤醒

    • 当持有锁的线程调用release()方法释放同步状态时,它会通过LockSupport.unpark()方法唤醒等待队列头部的下一个节点所对应的线程
    • 被唤醒的线程会再次尝试获取同步状态(state)。

2.4 独占模式 vs. 共享模式

AQS支持两种资源共享模式:

  • 独占模式 (Exclusive Mode):资源在同一时刻只能被一个线程持有。

    • 例子ReentrantLock
  • 共享模式 (Shared Mode):资源在同一时刻可以被多个线程持有。

    • 例子Semaphore(信号量)、CountDownLatchReadWriteLock中的读锁。

2.5 需要重写的核心方法 (模板方法)

开发者在实现自定义同步器时,需要根据选择的模式重写以下核心方法:

  • 独占模式

    • tryAcquire(int arg): 尝试以独占方式获取资源。
    • tryRelease(int arg): 尝试以独占方式释放资源。
    • isHeldExclusively(): 判断当前线程是否是独占持有资源。
  • 共享模式

    • tryAcquireShared(int arg): 尝试以共享方式获取资源。
    • tryReleaseShared(int arg): 尝试以共享方式释放资源。
http://www.jsqmd.com/news/279407/

相关文章:

  • 你还在用API模拟?MCP协议让AI直接操作文件系统(性能提升300%的秘密)
  • MCP协议权限配置全解析:确保AI Agent安全访问本地文件的7个关键步骤
  • Z-Image-Turbo成本控制:短时任务GPU按需启动实战指南
  • 详细介绍:解锁Python的强大能力:深入理解描述符
  • 2026年复合管激光堆焊厂家排名,广东好用的厂家推荐
  • 物联网墨水屏电子价签可以支持NFC刷新吗?
  • TurboDiffusion影视应用案例:分镜动态预览系统快速搭建教程
  • 低成本部署GPT-OSS-20B?微调显存需求与优化方案
  • AI推理框架选型指南:SGLang开源优势+GPU适配入门必看
  • MCP服务器resources动态扩展实践:应对高并发的4步速成方案
  • 2026朝阳市英语雅思培训辅导机构推荐,2026权威出国雅思课程排行榜
  • 2026年权威数据资产变现品牌方案推荐
  • 2026年探讨货架冲床设备、槽钢冲床设备源头厂家排名
  • midscene.js简介
  • 并发 - 分布式锁 (Distributed Lock) vs 本地锁 (Synchronized)
  • GPEN影视后期预研案例:老旧胶片数字修复流程探索
  • GPEN前端框架分析:Vue/React技术栈可能性推断
  • 又是新的一天
  • 【稀缺技术曝光】:大型系统中MCP服务器动态资源配置的黄金法则
  • Open-AutoGLM新闻阅读助手:热点资讯推送执行部署案例
  • 2026年济南雅思培训机构推荐及综合参考
  • 上海拆除公司、专业拆除公司、写字楼拆除公司、办公室拆除公司、商场拆除公司、室内拆除公司、室外拆除公司、工程拆除公司选择指南
  • 基于51单片机射频RFID卡签到考勤计数统计系统设计/DIY套件103(设计源文件+万字报告+讲解)(支持资料、图片参考_相关定制)_文章底部可以扫码
  • 20260121给荣品RD-RK3588开发板的荣品Android13打包APK的时候出现问题3797897216 > 3263168512
  • 2026年冷干机大型厂家排名,哪家性价比高值得选购?
  • 并发 - 原子类与 CAS 原理
  • 并发 - Callable 与 Future
  • 麦橘超然性能压测报告:单次生成耗时统计
  • 2026营口市英语雅思培训辅导机构推荐;2026权威出国雅思课程排行榜
  • fft npainting lama高阶使用技巧:分层修复与边缘羽化实战案例