深入理解 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 默认非公平,灵活性强。
  • 线程池 是并发任务管理的核心工具。

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

相关推荐
艾露z2 分钟前
深度解析Mysql中MVCC的工作机制
java·数据库·后端·mysql
前端付豪11 分钟前
揭秘网易统一日志采集与故障定位平台揭秘:如何在亿级请求中1分钟定位线上异常
前端·后端·架构
陈随易28 分钟前
Lodash 杀手来了!es-toolkit v1.39.0 已完全兼容4年未更新的 Lodash
前端·后端·程序员
未来影子1 小时前
SpringAI(GA):Nacos3下的分布式MCP
后端·架构·ai编程
Hockor1 小时前
写给前端的 Python 教程三(字符串驻留和小整数池)
前端·后端·python
码农之王1 小时前
记录一次,利用AI DeepSeek,解决工作中算法和无限级树模型问题
后端·算法
Wo3Shi4七1 小时前
消息不丢失:生产者收到写入成功响应后消息一定不会丢失吗?
后端·kafka·消息队列
爱上语文1 小时前
MyBatisPlus(3):常用配置
java·后端·mybatis
编程乐趣1 小时前
C#实现Stdio通信方式的MCP Server
后端
程序猿本员1 小时前
线程池精华
c++·后端