深入理解 Java 并发:CAS、AQS、ReentrantLock 与线程池

深入理解 Java 并发:CAS、AQS、ReentrantLock 与线程池

在 Java 并发编程中,CAS(Compare-And-Swap)、AQS(AbstractQueuedSynchronizer)、ReentrantLock 以及线程池是绕不开的核心话题。本文将基于面试常见问题,逐一剖析这些概念的原理、应用场景以及注意事项。

1. CAS 和 AQS 是什么?

CAS(Compare-And-Swap)

CAS 是一种无锁的原子操作,核心思想是通过比较和交换来更新变量值。它依赖硬件级别的指令(如 CPU 的 cmpxchg),通常包含三个参数:

  • 内存位置(V):要更新的变量。
  • 旧值(A):预期值。
  • 新值(B):要设置的值。

执行逻辑是:如果内存位置 V 的当前值等于旧值 A,则将 V 更新为新值 B;否则不做任何操作。CAS 的典型实现见 Java 的 AtomicInteger 类,例如 compareAndSet 方法。

AQS(AbstractQueuedSynchronizer)

AQS 是 Java 并发包(java.util.concurrent)的核心框架,用于构建锁和同步器。它通过一个基于 FIFO 队列的线程等待机制和一个 state 状态变量来管理线程同步。AQS 是许多同步工具(如 ReentrantLock、CountDownLatch、Semaphore)的基石。

两者的关系:

  • CAS 是 AQS 的底层实现手段之一。AQS 通过 CAS 操作 state 字段来实现线程的竞争和状态更新。
  • CAS 注重无锁操作的高效性,而 AQS 提供了一个通用的同步框架。

2. ReentrantLock 是公平锁还是非公平锁?

ReentrantLock 默认是非公平锁,但可以通过构造参数设置为公平锁:

java 复制代码
ReentrantLock lock = new ReentrantLock(); // 默认非公平锁
ReentrantLock fairLock = new ReentrantLock(true); // 公平锁
  • 非公平锁:新来的线程可能抢占刚释放的锁,吞吐量更高,但可能导致某些线程长时间等待。
  • 公平锁:严格按照线程请求锁的顺序分配,减少饥饿现象,但性能稍低(因为需要维护队列)。

底层实现上,ReentrantLock 基于 AQS,通过 state 表示锁的重入次数,非公平模式下会优先尝试 CAS 获取锁,而公平模式会检查等待队列。


3. 线程池是什么?

线程池是一种线程管理机制,通过复用线程来减少创建和销毁线程的开销。Java 的 ThreadPoolExecutor 是线程池的核心实现,构造参数包括:

  • 核心线程数(corePoolSize):常驻线程数。
  • 最大线程数(maximumPoolSize):允许的最大线程数。
  • 空闲线程存活时间(keepAliveTime):超出核心线程数的线程空闲多久后销毁。
  • 任务队列(workQueue):存储待执行任务的队列。
  • 拒绝策略(RejectedExecutionHandler):任务超载时的处理方式。

常见用法:

java 复制代码
ExecutorService executor = new ThreadPoolExecutor(
    2, 5, 60L, TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(10),
    new ThreadPoolExecutor.AbortPolicy()
);
executor.submit(() -> System.out.println("Task executed"));

线程池的优势在于资源复用、任务排队和并发控制,适用于高并发场景。


4. CAS 如果失败会发生什么?

CAS 失败意味着当前线程检测到内存值与预期值不一致,通常有以下处理方式:

  1. 自旋重试 :线程循环调用 CAS,直到成功。例如 AtomicIntegerincrementAndGet 方法:

    java 复制代码
    do {
        oldValue = get();
    } while (!compareAndSet(oldValue, oldValue + 1));
  2. 放弃或回退:根据业务逻辑选择放弃操作或执行其他策略。

  3. ABA 问题 :CAS 失败可能隐藏 ABA 问题(值从 A 变为 B 又变回 A),可以通过版本号(如 AtomicStampedReference)解决。

失败的原因通常是多线程竞争导致的并发修改。


5. CAS 和 AQS 基于什么场景使用?

  • CAS 适用场景

    • 单变量的原子更新,例如计数器(AtomicInteger)、标志位。
    • 轻量级、无锁操作,要求高性能且竞争不激烈。
    • 示例:乐观锁实现、简单的并发数据结构。
  • AQS 适用场景

    • 需要复杂的同步逻辑,如锁、信号量、倒计时器。
    • 多线程协作或资源竞争,例如线程安全的队列、读写锁。
    • 示例:ReentrantLockSemaphoreCountDownLatch

两者的选择取决于同步需求的复杂度和性能要求:CAS 简单高效,AQS 功能强大但稍重。


6. 为什么 AQS 适用于写的场景?

AQS 特别适合"写"场景的原因在于:

  1. 状态管理 :AQS 的 state 字段可以表示锁状态,通过 CAS 更新实现线程安全,天然适合写操作的竞争。
  2. 队列机制:写操作通常需要互斥,AQS 的 FIFO 队列确保线程按序获取资源,避免无序竞争。
  3. 灵活性:AQS 支持独占模式(如 ReentrantLock)和共享模式(如 Semaphore),能适应多种写场景。
  4. 重入支持 :写操作常涉及递归或嵌套调用,AQS 的重入机制(通过 state 计数)非常契合。

例如,ReentrantLock 在写多读少的场景中,通过 AQS 保证写操作的线程安全和高吞吐量。


总结

  • CAS 是无锁并发的基础,高效但适用范围有限。
  • AQS 是构建复杂同步器的利器,适合需要队列管理和状态控制的场景。
  • ReentrantLock 默认非公平,灵活性强。
  • 线程池 是并发任务管理的核心工具。

理解这些概念的原理和适用场景,不仅能应对面试,更能在实际开发中设计高效的并发系统。希望这篇博客对你的学习有所帮助!

相关推荐
Victor35615 分钟前
MongoDB(2)MongoDB与传统关系型数据库的主要区别是什么?
后端
JaguarJack16 分钟前
PHP 应用遭遇 DDoS 攻击时会发生什么 从入门到进阶的防护指南
后端·php·服务端
BingoGo17 分钟前
PHP 应用遭遇 DDoS 攻击时会发生什么 从入门到进阶的防护指南
后端
Victor35618 分钟前
MongoDB(3)什么是文档(Document)?
后端
牛奔2 小时前
Go 如何避免频繁抢占?
开发语言·后端·golang
想用offer打牌7 小时前
MCP (Model Context Protocol) 技术理解 - 第二篇
后端·aigc·mcp
KYGALYX9 小时前
服务异步通信
开发语言·后端·微服务·ruby
掘了9 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
爬山算法9 小时前
Hibernate(90)如何在故障注入测试中使用Hibernate?
java·后端·hibernate
Moment10 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端