Java锁机制:synchronized与ReentrantLock深度对比
Java锁机制:synchronized与ReentrantLock深度对比
分类:concurrent
摘要:锁是并发编程的核心概念,理解synchronized和ReentrantLock的区别和适用场景对写出高质量并发代码至关重要。本文将从底层原理、功能差异、实战代码及性能维度进行全面解析。
一、 引言
在Java并发编程的世界里,线程安全是不可逾越的大山,而“锁”则是攀登这座大山最核心的工具。提到Java锁,最经典的莫过于JVM原生提供的 synchronized 关键字和 JDK 1.5 引入的 ReentrantLock 类。
很多开发者在面试或实际开发中常常困惑:既然有了 synchronized,为什么还要造一个 ReentrantLock?它们到底有什么本质区别?在什么场景下该选择哪一个?本文将剥离表象,深入JVM底层与AQS原理,为你深度对比这两者,助你彻底掌握Java锁机制。
二、 核心概念与基础认知
1. synchronized:JVM原生基石
synchronized 是Java语言内置的关键字,属于JVM层面的实现。它就像一把自动化的防盗门,你只需要告诉它“锁哪里”,剩下的加锁、解锁过程完全由JVM负责。
- 特性:隐式加锁/解锁、可重入、非公平锁(默认)、不可中断。
- 作用范围:修饰普通方法(锁当前实例对象)、修饰静态方法(锁Class对象)、修饰代码块(指定锁对象)。
2. ReentrantLock:API层面的精密仪器
ReentrantLock 是 java.util.concurrent.locks 包下的一个类,基于AQS(AbstractQueuedSynchronizer)实现。它就像一把带各种高级功能的密码锁,需要开发者手动控制开启和关闭。
- 特性:显式加锁/解锁、可重入、支持公平/非公平选择、支持响应中断、支持超时获取锁。
- 核心API:
lock(),unlock(),tryLock(),lockInterruptibly()。
三、 底层实现原理深度剖析
要真正理解两者的区别,必须深入到底层看源码。
1. synchronized 的对象头与 Monitor
synchronized 的实现依赖于对象的 Monitor(监视器锁)。在JVM中,对象在内存中的布局分为:对象头、实例数据和对齐填充。
- 对象头:包含 Mark Word(存储锁状态、哈希值、GC年龄等)和类型指针。
- 锁升级过程:JDK 1.6 之后,为了优化
synchronized的性能,引入了“锁升级”机制:- 无锁:对象刚创建。
- 偏向锁:同一线程多次获取锁,只需修改Mark Word,无需CAS。
- 轻量级锁:不同线程交替执行,通过CAS将Mark Word替换为指向栈中Lock Record的指针。
- 重量级锁:竞争激烈,Mark Word指向堆中的Monitor对象,线程会阻塞,涉及用户态与内核态的切换,开销较大。
字节码层面,synchronized 代码块通过 monitorenter 和 monitorexit 两个指令实现。
2. ReentrantLock 与 AQS 架构
ReentrantLock 的核心在于 AQS(抽象队列同步器)。
- state 变量:AQS中维护了一个
volatile int state变量。state=0表示锁空闲;state>0表示锁被占用,值代表重入次数。 - CLH 队列:当锁竞争失败时,线程会被封装成 Node 节点,加入到一个双向链表(CLH队列变体)中,并调用
LockSupport.park()挂起线程。 - CAS 操作:获取锁的过程本质上是利用 CAS(Compare And Swap)原子指令修改
state变量。 - 公平性实现:
- 非公平锁(默认):直接尝试CAS抢锁,抢不到再排队。性能高,但可能导致线程饥饿。
- 公平锁:严格按照队列顺序获取锁,先来后到。
四、 核心区别对比
为了更直观地展示差异,我们整理如下对比表:
| 维度 | synchronized | ReentrantLock |
|---|---|---|
| 实现层面 | JVM关键字,C++实现 | Java API类,基于AQS |
| 锁的获取/释放 | 自动释放(JVM控制),代码块结束或异常时释放 | 必须手动释放(finally块中),否则死锁 |
| 锁的类型 | 仅仅支持非公平锁 | 支持公平锁和非公平锁(构造函数传参) |
| 中断响应 | 不支持,线程阻塞后无法中断,只能一直等 | 支持 lockInterruptibly(),解决死锁僵局 |
| 超时机制 | 不支持 | 支持 tryLock(timeout),获取不到立即返回 |
| 条件变量 | 单一条件,通过 wait/notify 实现 | 支持多个 Condition,可精细控制线程等待/唤醒 |
| 性能 | JDK 1.6优化后,低竞争下性能优异 | 高竞争下,AQS的CAS+自旋性能更稳定 |
五、 实战代码演练
场景一:基础加锁与手动解锁对比
synchronized 示例:
```java
/*
* synchronized 基础示例
* 模拟银行转账,synchronized 自动管理锁
/
public class SynchronizedDemo {
private int balance = 0;
// 修饰实例方法,锁对象为当前实例
public synchronized void deposit(int amount) {
// 进入方法自动加锁

浙公网安备 33010602011771号