面试复盘:synchronized 锁与 ReentrantLock 锁的区别及 AQS 认知完善
在最近的一次面试中,面试官问了我一个并发相关的问题:"synchronized
锁和 ReentrantLock
锁有什么区别?"当时我给出了基础的回答,但脑海中浮现了 AQS(AbstractQueuedSynchronizer)的概念。由于对 AQS 了解有限,我没敢深入延伸。现在通过复盘和查阅资料,我希望完善这块知识,同时建构一个结构化的表达话术,以便未来面试中更自信地回答类似问题。
一、面试时的回答与反思
1. 我的回答
当时我的回答是这样的:
"
synchronized
是 Java 的关键字,属于内置锁,简单易用,由 JVM 管理锁的获取和释放。ReentrantLock
是java.util.concurrent.locks
包下的类,属于显式的锁,需要手动加锁和释放锁,功能更灵活。比如,ReentrantLock
支持公平锁和非公平锁的选择,还能响应中断、设置等待时间。而synchronized
不支持这些特性,使用起来更简单,但灵活性较低。"
2. 反思
- 优点 :我提到了两者的基本区别,比如内置 vs 显式、简单 vs 灵活,也点出了
ReentrantLock
的几个特性(公平性、中断、超时)。 - 不足 :
- 回答比较表面,没能深入到实现原理,比如锁的底层机制。
- 想到
ReentrantLock
基于 AQS,但因不熟悉没敢展开,导致回答深度不够。 - 表达不够结构化,显得零散,没有清晰的逻辑框架。
3. 改进目标
通过复盘,我希望:
- 补充 AQS 的知识,完善对
ReentrantLock
底层实现的理解。 - 设计一个结构化的表达话术,逻辑清晰、层次分明。
二、synchronized 与 ReentrantLock 的区别(完善版)
经过查阅资料,我将两者的区别总结为以下几个维度,并融入 AQS 的理解:
1. 使用方式
-
synchronized :
-
关键字,内置于 JVM,直接修饰代码块或方法。
-
加锁和释放由 JVM 自动管理,无需手动操作。
-
示例:
javasynchronized (obj) { // 同步代码块 }
-
-
ReentrantLock :
-
显示锁类,需要手动调用
lock()
和unlock()
。 -
释放锁必须放在
finally
块中,避免异常导致锁未释放。 -
示例:
javaReentrantLock lock = new ReentrantLock(); lock.lock(); try { // 同步代码块 } finally { lock.unlock(); }
-
2. 功能特性
- synchronized :
- 只支持非公平锁(先到先得)。
- 不支持中断或超时等待。
- 简单直接,适合基本同步场景。
- ReentrantLock :
- 可选择公平锁(
new ReentrantLock(true)
)或非公平锁。 - 支持中断(
lockInterruptibly()
)、超时(tryLock(long, TimeUnit)
)。 - 提供条件变量(
Condition
),支持更复杂的同步逻辑。
- 可选择公平锁(
3. 性能
- synchronized :
- JDK 1.6 后优化显著,引入偏向锁、轻量级锁、重量级锁,性能接近
ReentrantLock
。 - 但在高竞争场景下,仍依赖操作系统内核态的互斥锁(Mutex),开销较大。
- JDK 1.6 后优化显著,引入偏向锁、轻量级锁、重量级锁,性能接近
- ReentrantLock :
- 基于 AQS 实现,通过 CAS(Compare-And-Swap)和自旋锁在用户态完成大部分操作,减少内核态切换。
- 在高并发场景下,性能可能略优于
synchronized
。
4. 底层实现
- synchronized :
- 由 JVM 实现,依赖对象头中的 Monitor(监视器锁)。
- 通过
monitorenter
和monitorexit
字节码指令控制。
- ReentrantLock :
- 基于 AQS 框架,内部维护一个 volatile 状态变量(
state
)和等待队列。 - 加锁通过 CAS 修改
state
,失败则进入 AQS 队列等待。
- 基于 AQS 框架,内部维护一个 volatile 状态变量(
5. 可重入性
- 两者都支持可重入性,即同一线程可多次获取同一把锁,不会自我阻塞。
三、AQS 的简单认知
ReentrantLock
的底层依赖 AQS(AbstractQueuedSynchronizer),这是 Java 并发包的核心框架。以下是我通过查阅资料整理的 AQS 基础:
- 核心思想 :
- AQS 维护一个
volatile int state
(表示锁状态)和一个双向链表队列(存放等待线程)。 state = 0
表示锁未被占用,state > 0
表示已加锁(支持重入,值递增)。
- AQS 维护一个
- 加锁过程 :
- 通过 CAS 尝试将
state
从 0 改为 1,成功则获取锁。 - 失败则将线程加入等待队列,进入阻塞状态。
- 通过 CAS 尝试将
- 释放锁 :
state
减 1,若减到 0,则唤醒队列中的下一个线程。
- 与 ReentrantLock 的关系 :
ReentrantLock
通过继承 AQS 的Sync
类实现锁逻辑。- 公平锁按队列顺序唤醒,非公平锁允许插队。
由于时间有限,我目前对 AQS 的理解停留在概览层面,后续需深入学习其源码(如 tryAcquire
、tryRelease
方法)。
四、结构化的表达话术设计
基于以上分析,我设计了以下面试表达话术,力求逻辑清晰、内容全面:
"面试官您好,关于
synchronized
和ReentrantLock
的区别,我可以从以下几个方面来回答:
第一,使用方式 :synchronized
是 JVM 内置的关键字,自动加锁和释放,用法简单;而ReentrantLock
是 JUC 包中的类,需要手动调用lock()
和unlock()
,更灵活但要求开发者注意释放锁。
第二,功能特性 :synchronized
只支持非公平锁,且不支持中断或超时;ReentrantLock
则提供公平/非公平锁选项,还支持中断、超时等待,甚至可以用Condition
实现复杂同步。
第三,性能表现 :早期synchronized
性能较差,但 JDK 1.6 后通过偏向锁和轻量级锁优化,已接近ReentrantLock
。不过在高竞争场景下,ReentrantLock
因基于 AQS 和 CAS,减少了内核态切换,可能更有优势。
第四,底层实现 :synchronized
依赖 JVM 的 Monitor,由对象头控制;ReentrantLock
基于 AQS,通过state
变量和等待队列管理锁状态。如果要延伸,
ReentrantLock
的 AQS 是一个通用同步框架,核心是维护一个状态值和线程队列,通过 CAS 实现高效加锁。我对 AQS 了解还不深入,但知道它是许多 JUC 工具(如CountDownLatch
)的基础,值得进一步学习。"
五、总结与计划
这次面试暴露了我对 AQS 的知识盲点,虽然基础回答没出错,但缺乏深度让我错失了展示能力的机会。通过复盘,我不仅完善了两者的区别,还初步理解了 AQS 的作用。未来计划:
- 阅读
ReentrantLock
和 AQS 的源码,掌握其实现细节。 - 练习结构化表达,提升回答的逻辑性和流畅性。