Java 并发锁核心解析:synchronized vs ReentrantLock(含底层原理)
Java 并发锁核心解析:synchronized vs ReentrantLock(含底层原理)
在 Java 并发体系中,synchronized 和 ReentrantLock 是最核心的两种锁实现。面试与实际开发中都高频出现。本文从底层实现机制出发,系统梳理二者原理,并在最后进行简洁对比。
目录
- Java 并发锁核心解析:synchronized vs ReentrantLock(含底层原理)
一、synchronized 介绍及底层原理
1.1 基本介绍
synchronized 是 Java 内置的关键字锁,也称为监视器锁(Monitor Lock),属于 JVM 层面的实现。
它用于保证:
- 互斥访问(同一时刻只有一个线程执行临界区)
- 可见性(释放锁时刷新主内存)
使用方式:
1 | synchronized (obj) { |
或方法级别:
1 | public synchronized void test() {} |
1.2 底层实现机制
synchronized 的底层依赖 JVM 的对象头与 Monitor(管程机制)。
1.2.1 对象头(Mark Word)
每个 Java 对象都包含对象头,其中 Mark Word 用于存储锁信息:
- 无锁状态
- 偏向锁线程ID
- 轻量级锁指针
- 重量级锁 Monitor 指针
1.2.2 锁升级机制
synchronized 在 JVM 中会根据竞争情况自动升级:
1 | 无锁 → 偏向锁 → 轻量级锁 → 重量级锁 |
偏向锁
- 适用于无竞争场景
- 记录第一次获取锁的线程
轻量级锁
- 使用 CAS 自旋竞争
- 避免线程阻塞
重量级锁
- 竞争激烈时升级
- 依赖 OS Mutex
- 线程进入阻塞状态
1.2.3 Monitor(监视器)
当升级为重量级锁时,会使用 Monitor:
- Entry List:竞争队列
- Wait Set:等待队列
- Owner:持有锁的线程
👉 本质:线程通过 Monitor 实现阻塞与唤醒。
1.3 synchronized 特点总结
- JVM 内置实现
- 自动加锁/释放锁
- 支持锁升级优化
- 不可中断
- 不支持超时获取
二、ReentrantLock 介绍及底层原理
2.1 基本介绍
ReentrantLock 是 JUC 提供的可重入锁类,属于 JDK 层实现。
相比 synchronized,它提供更多可控能力:
- 可中断
- 可超时
- 公平/非公平锁
- 多条件变量
2.2 底层核心:AQS
ReentrantLock 的核心依赖:
👉 AbstractQueuedSynchronizer
AQS 是一个同步框架,用于构建锁与同步器。
2.3 AQS 核心结构
2.3.1 state(同步状态)
1 | volatile int state; |
state 是 AQS 的核心变量,但它不是锁本身,而是同步状态的抽象:
| 场景 | state 含义 |
|---|---|
| ReentrantLock | 重入次数 |
| Semaphore | 资源数量 |
| CountDownLatch | 计数器 |
2.3.2 CLH 队列(等待队列)
AQS 维护一个 FIFO 双向队列:
1 | Head → Node → Node → Node |
每个 Node 表示一个线程:
1 | Thread thread; |
用于管理阻塞线程的排队顺序。
2.3.3 CAS + LockSupport
- CAS:用于抢占 state
- LockSupport:用于阻塞/唤醒线程
1 | LockSupport.park(); |
2.4 ReentrantLock 加锁流程
2.4.1 Step 1:CAS 尝试加锁
1 | compareAndSetState(0, 1) |
成功 → 当前线程获得锁
2.4.2 Step 2:失败 → 入队
线程加入 AQS 队列尾部:
1 | Thread → CLH Queue |
2.4.3 Step 3:自旋 + 阻塞
1 | for (;;) { |
本质:先竞争,再阻塞
2.5 ReentrantLock 解锁流程
2.5.1 Step 1:state 减 1
1 | state-- |
2.5.2 Step 2:完全释放
当 state == 0:
- 释放锁持有线程
- 设置 owner = null
2.5.3 Step 3:唤醒后继线程
1 | LockSupport.unpark(nextThread); |
2.6 可重入机制
1 | if (currentThread == ownerThread) { |
释放时:
1 | state--; |
同一线程可重复获取锁,不会阻塞。
2.7 Condition(条件变量)
ReentrantLock 支持多个条件队列:
1 | Condition c1 = lock.newCondition(); |
机制:
- await() → 进入 Condition 队列
- signal() → 转移到 AQS 队列
2.8 公平 vs 非公平
非公平锁(默认)
- CAS直接抢锁
- 允许插队
公平锁
1 | if (hasQueuedPredecessors()) return false; |
按队列顺序执行
三、synchronized vs ReentrantLock 对比总结
synchronized与ReentrantLock的对比主要在于以下四个方面:- 可重入性:
synchronized是可重入锁,ReentrantLock也是可重入锁。 - 依赖:
synchronized依赖于JVM,而ReentrantLock依赖于API。 - 功能:
ReentrantLock相比synchronized提供了更多的功能,主要有四点:- 等待可中断
- 可实现公平锁
- 可实现选择性通知
- 支持超时
- 是否可中断:
ReentrantLock属于可中断锁,而synchronized属于不可中断锁。
- 可重入性:
3.1 可重入性
- 可重入锁也叫递归锁,指的是线程可以再次获取自己的内部锁。
- JDK提供的所有现成的
Lock实现类,以及synchronized关键字都是可重入锁。 - 示例:
1
2
3
4
5
6
7
8
9
10public class SynchronizedDemo {
public synchronized void methodA() {
System.out.println("In method A");
methodB(); // 再次获取锁
}
public synchronized void methodB() {
System.out.println("In method B");
}
} - 在上述示例中,同一个线程在调用
methodA时获取了当前对象的锁,执行methodB时可以再次获取当前对象的锁,不会产生死锁问题。
3.2 依赖
synchronized是Java语言内置的同步机制,依赖于JVM的实现。ReentrantLock是JDK层面实现的,也就是API层面,需要使用lock()和unlock()方法配合try/finally来使用。
3.3 功能
ReentrantLock相比synchronized提供了更多的功能,主要有以下四点:- 等待可中断:
ReentrantLock中的lock.lockInterruptibly()方法允许线程在等待锁的过程中被中断。也就是当前线程在等待获取锁的过程中被其他线程使用interrupt()中断,当前线程就会抛出InterruptedException异常,可以捕捉该异常并进行处理。 - 可实现公平锁:
ReentrantLock可以选择使用公平锁还是非公平锁,而synchronized只能是非公平锁。公平锁是指线程获取锁的顺序是按照请求锁的顺序来分配的,而非公平锁则不保证顺序。ReentrantLock默认使用非公平锁。1
2
3
4// 传入一个boolean参数,true表示公平锁,false表示非公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
} - 可实现选择性通知:
synchronized关键字与wait()和notify()/notifyAll()方法相结合可以实现等待/通知机制。但是synchronized只能使用一个等待队列,而ReentrantLock可以通过Condition接口和newCondition)_方法创建多个等待队列,从而实现更灵活的等待/通知机制。 - 支持超时:
ReentrantLock提供的tryLock(timeout)方法可以指定等待获取锁的最长等待时间,超过这个时间就会获取锁失败,不会一直等待。
- 等待可中断:
3.4 是否可中断
ReentrantLock属于可中断锁,而synchronized属于不可中断锁。
3.5 对比总结
| 维度 | synchronized | ReentrantLock |
|---|---|---|
| 实现层级 | JVM | JDK(AQS) |
| 核心机制 | Monitor + 对象头 | CAS + AQS队列 |
| 可重入 | 支持 | 支持 |
| 公平性 | 不支持 | 支持 |
| 可中断 | 不支持 | 支持 |
| 超时机制 | 不支持 | 支持 |
| 条件变量 | 单一 wait/notify | 多 Condition |
| 使用复杂度 | 简单 | 较复杂 |
四、总结
synchronized 是 JVM 层面的自动优化锁,适合简单场景;
ReentrantLock 基于 AQS 构建,提供更强的灵活性与控制能力,适用于复杂并发场景。
