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

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

相关推荐
HelloZheQ1 小时前
Go:简洁高效,构建现代应用的利器
开发语言·后端·golang
caihuayuan51 小时前
[数据库之十四] 数据库索引之位图索引
java·大数据·spring boot·后端·课程设计
风象南2 小时前
Redis中6种缓存更新策略
redis·后端
程序员Bears3 小时前
Django进阶:用户认证、REST API与Celery异步任务全解析
后端·python·django
非晓为骁3 小时前
【Go】优化文件下载处理:从多级复制到零拷贝流式处理
开发语言·后端·性能优化·golang·零拷贝
北极象3 小时前
Golang中集合相关的库
开发语言·后端·golang
喵手3 小时前
Spring Boot 中的事务管理是如何工作的?
数据库·spring boot·后端
玄武后端技术栈5 小时前
什么是延迟队列?RabbitMQ 如何实现延迟队列?
分布式·后端·rabbitmq
液态不合群6 小时前
rust程序静态编译的两种方法总结
开发语言·后端·rust
bingbingyihao6 小时前
SpringBoot教程(vuepress版)
java·spring boot·后端