ReentrantLock 深层分析:CAS、AQS原理全揭秘
此文为「Java 深度与实战·每日一读」系列第1篇,原创专栏,全篇不含水分,该系列整个面向:初学、进阶、面试、原理、实战,全综合型导向。
目标:让任何级别的 Java 学习者,看不懂也要收藏,看得懂就能高速成长!
目录
- ReentrantLock 深层分析:CAS、AQS原理全揭秘
- @[toc](目录)
- 一、基础矩阵:ReentrantLock 的概念
- 二、CAS 原理完全解析
- 2.1 CAS 是什么?
- 2.2 CAS 实现原7406
- 2.3 CAS 定位的问题:
- 三、AQS 原理全揭秘
- 3.1 AQS 是什么?
- 3.2 AQS 工作流程
- 3.3 AQS 的两种锁模式
- 四、ReentrantLock 实现原理
- 4.1 主要组成
- 4.2 上锁流程
- 4.3 重入的原理
- 五、实战示例:体验一下经典场景
- 5.1 例子:多线程清单操作
- 5.2 例子:异常处理
- 六、深层思考:ReentrantLock 比最原始的锁属性有什么优势?
- 七、原理其实一点不难:简单就是最大的备考利器
- 🗂️八、面试问题目录
- 🔥 高频面试真题解析 · ReentrantLock / CAS / AQS 篇
- 一、ReentrantLock 相关
- 1. 什么是 ReentrantLock?与 synchronized 有什么区别?
- 2. 为什么叫“可重入锁”?它如何实现可重入?
- 3. ReentrantLock 支持哪些锁类型?怎么设置?
- 4. 如何正确使用 ReentrantLock,避免死锁?
- 5. tryLock 有什么应用场景?与 lock() 有什么不同?
- 6. ReentrantLock 是怎么保证线程安全的?内部用到了哪些底层机制?
- 二、CAS 相关
- 1. CAS 原理是什么?CAS 操作是如何保证原子性的?
- 2. CAS 操作失败会发生什么?什么是自旋?
- 3. CAS 有哪些典型问题?如何解决 ABA 问题?
- 4. CAS 和传统的加锁机制相比,有哪些优缺点?
- 5. Java 中有哪些类用到了 CAS?
- 三、AQS 相关
- 1. 什么是 AQS?它在 Java 并发框架中起到了什么作用?
- 2. AQS 的核心设计是什么?
- 四、综合类问题(高频+进阶)
- 1. 如何自己实现一个简单的可重入锁(MyLock)?
- 2. 高并发场景下,什么时候使用 CAS?什么时候应该使用锁?
- 🙏 感谢阅读!
目录
- ReentrantLock 深层分析:CAS、AQS原理全揭秘
- @[toc](目录)
- 一、基础矩阵:ReentrantLock 的概念
- 二、CAS 原理完全解析
- 2.1 CAS 是什么?
- 2.2 CAS 实现原7406
- 2.3 CAS 定位的问题:
- 三、AQS 原理全揭秘
- 3.1 AQS 是什么?
- 3.2 AQS 工作流程
- 3.3 AQS 的两种锁模式
- 四、ReentrantLock 实现原理
- 4.1 主要组成
- 4.2 上锁流程
- 4.3 重入的原理
- 五、实战示例:体验一下经典场景
- 5.1 例子:多线程清单操作
- 5.2 例子:异常处理
- 六、深层思考:ReentrantLock 比最原始的锁属性有什么优势?
- 七、原理其实一点不难:简单就是最大的备考利器
- 🗂️八、面试问题目录
- 🔥 高频面试真题解析 · ReentrantLock / CAS / AQS 篇
- 一、ReentrantLock 相关
- 1. 什么是 ReentrantLock?与 synchronized 有什么区别?
- 2. 为什么叫“可重入锁”?它如何实现可重入?
- 3. ReentrantLock 支持哪些锁类型?怎么设置?
- 4. 如何正确使用 ReentrantLock,避免死锁?
- 5. tryLock 有什么应用场景?与 lock() 有什么不同?
- 6. ReentrantLock 是怎么保证线程安全的?内部用到了哪些底层机制?
- 二、CAS 相关
- 1. CAS 原理是什么?CAS 操作是如何保证原子性的?
- 2. CAS 操作失败会发生什么?什么是自旋?
- 3. CAS 有哪些典型问题?如何解决 ABA 问题?
- 4. CAS 和传统的加锁机制相比,有哪些优缺点?
- 5. Java 中有哪些类用到了 CAS?
- 三、AQS 相关
- 1. 什么是 AQS?它在 Java 并发框架中起到了什么作用?
- 2. AQS 的核心设计是什么?
- 四、综合类问题(高频+进阶)
- 1. 如何自己实现一个简单的可重入锁(MyLock)?
- 2. 高并发场景下,什么时候使用 CAS?什么时候应该使用锁?
- 🙏 感谢阅读!
一、基础矩阵:ReentrantLock 的概念
ReentrantLock(重入锁),是一种在 JDK1.5 引入的是符合 java.util.concurrent.locks.Lock 接口的实现类,它对 synchronized 进行了很多封装和扩展:
- 可以手动锁 / 释放,更灵活
- 支持多种锁(全局锁/全局重入锁)
- 支持反应不同类型的等待(可不可为)
但是,其实现原理,非常高级!
二、CAS 原理完全解析
2.1 CAS 是什么?
CAS(Compare And Swap):比较并且替换。
通过 CPU 原定命令,把 目标内存地址的值 与 预期值 比较,如果相等,则更新成新值;否则,不做操作。
操作是原定的,一步到位,避免线程竞争。
2.2 CAS 实现原7406
常见实现:
- x86 系列 CPU :通过
cmpxchg
- ARM 系列 CPU :使用 LDREX/STREX
JDK 中,主要通过 sun.misc.Unsafe 实现。
Unsafe.compareAndSwapInt(Object obj, long offset, int expect, int update)
2.3 CAS 定位的问题:
问题 | 解决方案 |
---|---|
ABA 问题 | AtomicStampedReference |
自旋耗费 CPU | 合理控制自旋次数 |
不能操作多个值 | AtomicReference 或使用锁 |
三、AQS 原理全揭秘
3.1 AQS 是什么?
AbstractQueuedSynchronizer,抽象队列合并器
基于 FIFO 队列,提供一种构建高级合并器(如:ReentrantLock, CountDownLatch)的通用框架。
核心组件:
- state :锁的状态值(int)
- CLH 队列 :线程坐标队列
- Node :表示每个线程
3.2 AQS 工作流程
- CAS 抽奖默认拥有者权限
- 失败的线程进入队列排队等待
- 拥有者释放时,通知队头线程
- 队头线程重新挑战 CAS
图示:
线程A(锁住) -> 线程B(等待) -> 线稌C(等待)
它们就排成一条队。
3.3 AQS 的两种锁模式
- 单种上锁(一个线程拥有)
- 共享锁(多线程同时拥有,如 ReadWriteLock)
四、ReentrantLock 实现原理
4.1 主要组成
- Sync:内部抽象类,继承 AQS
- NonfairSync / FairSync:非公平和公平版本
4.2 上锁流程
- 尝试通过 CAS 拥有 state=1
- 失败:入队排队,等待
- 释放时,把 state=0,并 unpark 队头线程
4.3 重入的原理
同一线程再次上锁,state++,需要释放多次才能真正释放锁。
五、实战示例:体验一下经典场景
5.1 例子:多线程清单操作
Lock lock = new ReentrantLock();
public void clearCart() {
lock.lock();
try {
// 清空购物车
cart.clear();
} finally {
lock.unlock();
}
}
5.2 例子:异常处理
必须放在 finally 中,否则导致泛锁,系统失效。
六、深层思考:ReentrantLock 比最原始的锁属性有什么优势?
方面 | synchronized | ReentrantLock |
---|---|---|
解锁 | 自动 | 手动 |
应急中断 | 不支持 | 支持(lockInterruptibly) |
时间等待 | 不支持 | 支持(tryLock) |
公平性 | 不保证 | 可选公平 |
七、原理其实一点不难:简单就是最大的备考利器
只需记住:
- CAS 保证原定性
- AQS 通过队列来管理多个线程
- ReentrantLock 给了更灵活的接口,更符合实际场景
🗂️八、面试问题目录
🔥 高频面试真题解析 · ReentrantLock / CAS / AQS 篇
点击直接跳转查看详细解析👇
- 一、ReentrantLock 相关
- 1. 什么是 ReentrantLock?与 synchronized 有什么区别?
- 2. 为什么叫“可重入锁”?它如何实现可重入?
- 3. ReentrantLock 支持哪些锁类型?怎么设置?
- 4. 如何正确使用 ReentrantLock,避免死锁?
- 5. tryLock 有什么应用场景?与 lock() 有什么不同?
- 6. ReentrantLock 是怎么保证线程安全的?内部用到了哪些底层机制?
- 二、CAS 相关
- 1. CAS 原理是什么?CAS 操作是如何保证原子性的?
- 2. CAS 操作失败会发生什么?什么是自旋?
- 3. CAS 有哪些典型问题?如何解决 ABA 问题?
- 4. CAS 和传统的加锁机制相比,有哪些优缺点?
- 5. Java 中有哪些类用到了 CAS?
- 三、AQS 相关
- 1. 什么是 AQS?它在 Java 并发框架中起到了什么作用?
- 2. AQS 的核心设计是什么?
- 3. AQS 支持哪两种模式?分别有哪些代表性实现?
- 4. AQS 如何实现线程挂起和唤醒?
- 5. 如何基于 AQS 自定义一个同步器?
- 四、综合类问题(高频+进阶)
- 1. 如何自己实现一个简单的可重入锁(MyLock)?
- 2. 高并发场景下,什么时候使用 CAS?什么时候应该使用锁?
- 3. 公平锁和非公平锁的区别?使用场景?
- 4. ReentrantLock 在高并发下会不会出现性能问题?原因是什么?
- 5. synchronized、Lock、原子类三者怎么选?
一、ReentrantLock 相关
1. 什么是 ReentrantLock?与 synchronized 有什么区别?
ReentrantLock
是一种显式的锁机制,它属于 Java 的 java.util.concurrent
包,提供了比 synchronized
更强大的功能,如公平性设置、可中断等。
- ReentrantLock 是可重入的,表示一个线程可以多次获取同一把锁,而不至于被自己阻塞。
- 与 synchronized 区别:
ReentrantLock
支持 可中断锁,而synchronized
不支持。ReentrantLock
可以进行 公平性设置,确保锁被最久等待的线程先获得。ReentrantLock
提供 tryLock() 方法,能设置锁的超时时间,而synchronized
没有此功能。
2. 为什么叫“可重入锁”?它如何实现可重入?
“可重入”是指一个线程可以多次请求获取同一个锁,而不会发生死锁。线程再次请求时,不会被阻塞,直到线程释放锁。
- 实现原理:
ReentrantLock
内部使用一个计数器来记录获取锁的次数,当前线程每获取一次锁,计数器加1。- 当计数器为0时,锁被释放。也就是说,锁的释放是通过计数器控制的。
3. ReentrantLock 支持哪些锁类型?怎么设置?
ReentrantLock
提供两种类型的锁:
- 公平锁:线程获取锁的顺序为先到先得,适合任务对锁公平性要求较高的场景。
- 非公平锁:线程获取锁的顺序不保证公平,适合大多数高性能场景。
可以通过 ReentrantLock(true)
设置公平锁,默认为非公平锁。
4. 如何正确使用 ReentrantLock,避免死锁?
死锁通常发生在多个线程相互持有锁且等待对方释放锁的场景。避免死锁的常见策略包括:
- 避免锁嵌套:减少线程在持有锁的情况下进行其它操作。
- 锁定顺序:确保多个线程获取多个锁时,锁的顺序一致。
5. tryLock 有什么应用场景?与 lock() 有什么不同?
tryLock()
方法可以设置超时来尝试获取锁,如果在规定时间内获取不到锁,返回 false
。
- 应用场景:适用于尝试获取锁的操作,如果获取不到锁,可以继续执行其他任务。
- 与 lock() 的区别:
lock()
是阻塞式的,一直等待获取锁;而tryLock()
是非阻塞的,可以设置等待超时。
6. ReentrantLock 是怎么保证线程安全的?内部用到了哪些底层机制?
ReentrantLock
通过 AQS(AbstractQueuedSynchronizer) 框架实现线程安全。它通过 CAS(Compare And Swap) 技术实现原子操作,避免了竞争条件,并通过队列机制来管理等待线程。
二、CAS 相关
1. CAS 原理是什么?CAS 操作是如何保证原子性的?
CAS 是一种乐观锁机制,它通过比较内存中的值和预期值,如果相同,则更新为新值;如果不同,则不做任何操作。这样保证了多个线程并发执行时,只有一个线程能够成功更新。
- 原子性保证:CAS 操作的原子性是由硬件支持的(如 CPU 中的原子指令)。
2. CAS 操作失败会发生什么?什么是自旋?
- CAS 操作失败时,意味着有其他线程对目标变量进行了修改。此时,CAS 操作会重试,直到成功为止。
- 自旋:CAS 失败后,会进入自旋状态,即线程不会被挂起,而是反复尝试执行 CAS 操作,直到成功或超时。
3. CAS 有哪些典型问题?如何解决 ABA 问题?
- ABA 问题:在 CAS 操作中,检查到值从 A 变成 B 后,又变回 A,导致线程误以为值没有改变。
- 解决方法:使用 版本号(即带时间戳的 CAS),通过额外的标记位来解决 ABA 问题。
4. CAS 和传统的加锁机制相比,有哪些优缺点?
- 优点:CAS 操作不需要加锁,避免了线程上下文切换,提高了并发性能。
- 缺点:CAS 会导致 ABA 问题,并且在长时间自旋的情况下,可能会导致 CPU 占用过高。
5. Java 中有哪些类用到了 CAS?
Java 中许多类使用 CAS 来优化性能,主要包括:
AtomicInteger
AtomicReference
ReentrantLock
ConcurrentLinkedQueue
三、AQS 相关
1. 什么是 AQS?它在 Java 并发框架中起到了什么作用?
AQS(AbstractQueuedSynchronizer)是 Java 提供的一个用于实现同步器的框架。它通过维护一个队列来管理请求同步的线程,从而实现锁、信号量等并发控制结构。
2. AQS 的核心设计是什么?
AQS 的核心设计是一个 FIFO(先入先出)队列,用来存储等待获取锁的线程。每个线程都通过 CAS 操作来尝试获取锁,成功的线程进入临界区,失败的线程进入等待队列。
四、综合类问题(高频+进阶)
1. 如何自己实现一个简单的可重入锁(MyLock)?
class MyLock {
private int count = 0;
private Thread currentThread = null;
public synchronized void lock() {
Thread thread = Thread.currentThread();
while (currentThread != thread && count > 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
currentThread = thread;
count++;
}
public synchronized void unlock() {
if (Thread.currentThread() == currentThread) {
count--;
if (count == 0) {
currentThread = null;
notify();
}
}
}
}
2. 高并发场景下,什么时候使用 CAS?什么时候应该使用锁?
- 使用 CAS:适合无锁操作且对性能要求极高的场景,如计数器更新。
- 使用锁:适用于有多个线程访问共享资源时,需要保证原子性和数据一致性,且数据量较大时使用锁更安全。
🙏 感谢阅读!
感谢大家阅读!如果你觉得这篇文章对你有所帮助,欢迎:
🔹 点赞
🔹 收藏
🔹 分享给更多需要的朋友
如果你有任何问题或者想深入讨论的内容,欢迎在评论区留言,或者私信我!你的每一条反馈都是我持续创作的动力!💪