Synchronized与ReentrantLock深入剖析:从表层到底层
在Java并发编程中,synchronized和ReentrantLock是两种最常用的锁机制,它们在多线程环境下保障线程安全。面试官常常通过这两个锁作为切入点,考察候选人对Java并发机制(JUC)的理解深度。本文将从表层比较开始,逐步深入到锁的底层实现,剖析synchronized的锁升级、对象头Mark Word、互斥锁系统调用,以及ReentrantLock背后的AQS结构、模板方法和CAS机制,展示清晰的知识体系和逻辑脉络。
一、Synchronized与ReentrantLock的表层比较
1.1 基本概念
- Synchronized:Java关键字,内置锁机制,基于JVM实现,适用于方法或代码块的同步。自动获取和释放锁,简单易用。
- ReentrantLock:JUC包中的显式锁,基于AQS(AbstractQueuedSynchronizer)实现,提供更灵活的功能,如公平锁、条件变量等。
1.2 异同点比较
以下是两者在功能、性能和使用场景上的对比:
| 特性 | Synchronized | ReentrantLock |
|---|---|---|
| 锁类型 | 内置锁(JVM级别) | 显式锁(Java API级别) |
| 可重入性 | 支持(同一个线程可多次获取锁) | 支持(基于计数器实现) |
| 公平性 | 非公平锁(可能导致线程饥饿) | 可选公平锁或非公平锁 |
| 灵活性 | 自动获取/释放锁,功能简单 | 支持tryLock、lockInterruptibly等高级功能 |
| 条件变量 | 单一等待队列(wait/notify) | 支持多个Condition对象 |
| 中断响应 | 不支持锁中断 | 支持(lockInterruptibly) |
| 性能 | JDK 6后优化(锁升级机制),性能接近ReentrantLock | 高并发场景下,非公平锁性能略优 |
| 使用场景 | 简单同步场景(如方法级别同步) | 复杂场景(需要公平锁、条件变量、超时机制) |
1.3 选择建议
- Synchronized:适合简单场景,代码简洁,JVM优化成熟,推荐用于低并发或简单同步。
- ReentrantLock:适合复杂场景,如需要公平锁、条件变量或中断响应,推荐用于高并发或复杂逻辑。
二、Synchronized的底层实现
synchronized的实现依赖JVM和操作系统的互斥机制,核心在于对象头(Mark Word)和锁升级机制。
2.1 对象头与Mark Word
Java对象在内存中由三部分组成:对象头、实例数据和对齐填充。对象头包含:
- Mark Word:存储锁状态、哈希码、GC标记等。
- Class Metadata Address:指向类的元数据。
Mark Word的结构(64位JVM):
| 状态 | Mark Word结构(64位) |
|---|---|
| 无锁 | `hash:31 |
| 偏向锁 | `thread:54 |
| 轻量级锁 | `ptr_to_lock_record:62 |
| 重量级锁 | `ptr_to_monitor:62 |
- lock字段:2位标志锁状态(01: 无锁/偏向锁,00: 轻量级锁,10: 重量级锁)。
- biased_lock:1位标志是否启用偏向锁。
- thread:偏向锁记录持有锁的线程ID。
- ptr_to_lock_record:轻量级锁指向栈中的锁记录。
- ptr_to_monitor:重量级锁指向Monitor对象。
2.2 锁升级过程
为了提高性能,synchronized采用锁升级机制,从无锁逐步升级到重量级锁:
-
无锁:对象刚创建,无线程竞争。
-
偏向锁:
- 启用条件:只有一个线程访问同步块。
- 实现:Mark Word记录线程ID,线程再次进入无需CAS。
- 开销:极低,仅需检查线程ID。
- 撤销:其他线程竞争时,暂停持有锁的线程,升级为轻量级锁。
-
轻量级锁:
- 启用条件:多个线程交替竞争,但无激烈争用。
- 实现:线程在栈中创建Lock Record,尝试用CAS将Mark Word替换为Lock Record指针。
- 开销:CAS操作,低于重量级锁。
- 升级:竞争加剧(如自旋失败),升级为重量级锁。
-
重量级锁:
- 启用条件:激烈竞争,线程需要阻塞。
- 实现:JVM创建Monitor对象,Mark Word指向Monitor,线程进入等待队列。
- 开销:高,涉及系统调用(如Linux的
futex)。
2.3 互斥锁的系统调用
重量级锁依赖操作系统内核的互斥锁(Mutex)。在Linux上,JVM通过futex(Fast Userspace Mutex)系统调用实现:
- 加锁:调用
futex检查锁状态,若锁被占用,线程进入内核态等待。 - 释放锁:调用
futex唤醒等待线程。 - 开销:用户态到内核态切换,上下文切换成本高。
三、ReentrantLock的底层实现
ReentrantLock基于AQS(AbstractQueuedSynchronizer),通过CAS和队列实现高效锁机制。
3.1 AQS核心结构
AQS是一个同步框架,核心组件包括:
- state:同步状态(int类型,
ReentrantLock中表示锁的重入次数)。 - CLH队列:双向链表,存储等待线程(Node对象)。
- Node:队列节点,包含线程、等待状态(SHARED/EXCLUSIVE)等。
AQS提供两种模式:
- 独占模式:如
ReentrantLock,一次只有一个线程持有锁。 - 共享模式:如
Semaphore,允许多个线程共享资源。
3.2 ReentrantLock的实现
ReentrantLock通过内部类Sync(继承AQS)实现锁逻辑:
-
非公平锁(NonfairSync) :
- 线程直接尝试CAS获取锁(
compareAndSetState),成功则持有锁,失败则入队。 - 优点:减少上下文切换,提高吞吐量。
- 缺点:可能导致线程饥饿。
- 线程直接尝试CAS获取锁(
-
公平锁(FairSync) :
- 检查队列是否有等待线程(
hasQueuedPredecessors),若有则入队,否则尝试获取锁。 - 优点:保证先来先得,减少饥饿。
- 缺点:频繁检查队列,性能略低。
- 检查队列是否有等待线程(
加锁流程:
- 调用
lock(),尝试通过CAS设置state(0->1)。 - 若成功,设置当前线程为锁持有者。
- 若失败,加入CLH队列,线程挂起(
LockSupport.park)。 - 锁释放时,唤醒队列头部线程(
LockSupport.unpark)。
重入性:
- 若同一线程再次加锁,
state递增(state++)。 - 释放锁时,
state递减,直到state=0才完全释放。
3.3 模板方法模式
AQS采用模板方法模式,将锁的具体逻辑交给子类实现:
-
核心方法:
tryAcquire:尝试获取锁(子类实现)。tryRelease:尝试释放锁(子类实现)。acquireQueued:入队并等待(AQS实现)。
-
ReentrantLock实现:
NonfairSync.tryAcquire:非公平尝试获取锁。FairSync.tryAcquire:检查队列后尝试获取锁。
3.4 CAS机制
CAS(Compare-And-Swap)是ReentrantLock的核心原子操作,基于硬件指令(如cmpxchg):
-
原理:比较内存值与预期值,若相等则更新为新值。
-
实现:Java通过
Unsafe类的compareAndSwapInt调用本地方法。 -
问题:
- ABA问题:值从A->B->A,可能掩盖变化。AQS通过
state单调递增避免。 - 自旋开销:CAS失败时可能多次重试,消耗CPU。
- ABA问题:值从A->B->A,可能掩盖变化。AQS通过
-
优化:AQS结合队列和
LockSupport,减少无谓自旋。
四、知识体系与脉络
通过synchronized和ReentrantLock的剖析,我们可以构建以下JUC知识体系:
-
锁机制:
synchronized:JVM内置锁,依赖对象头和Monitor。ReentrantLock:AQS实现的显式锁,灵活性强。
-
并发原语:
- CAS:原子操作,
ReentrantLock和AQS的核心。 LockSupport:线程阻塞与唤醒,AQS队列管理的基础。
- CAS:原子操作,
-
锁优化:
synchronized:偏向锁、轻量级锁、重量级锁。ReentrantLock:公平/非公平锁、条件变量。
-
底层支撑:
- 对象头(Mark Word):锁状态存储。
- 系统调用(futex):重量级锁的内核支持。
- AQS:JUC的核心框架,支撑
ReentrantLock、Semaphore等。
面试应对脉络:
- 表层:从功能比较入手,清晰说明两者的异同和使用场景。
- 中层:深入锁升级(
synchronized)和AQS(ReentrantLock),展示对机制的理解。 - 底层:剖析Mark Word、系统调用、CAS等,体现对JVM和操作系统的掌握。
- 扩展:结合JUC其他组件(如
Condition、CountDownLatch),展示知识广度。
五、总结
synchronized和ReentrantLock各有千秋,前者简单高效,适合简单场景;后者灵活强大,适合复杂并发逻辑。通过锁升级、Mark Word、AQS、CAS等底层机制的剖析,我们不仅理解了两者的实现原理,还构建了从表层到底层的JUC知识体系。在面试中,清晰的逻辑、深入的分析和全面的知识脉络将帮助你从容应对面试官的“拷打”。