一、Synchronized锁升级:从偏向锁到重量级锁
1. 锁升级流程
Synchronized的锁状态通过对象头中的Mark Word表示,升级路径如下:
无锁 → 偏向锁 → 轻量级锁 → 重量级锁
2. 重量级锁详解
触发条件:
- 多个线程竞争同一锁。
- 轻量级锁自旋超过阈值(默认10次,JVM自适应调整)。
实现原理:
- Monitor对象:每个Java对象关联一个
Monitor(由C++实现,位于JVM的ObjectMonitor.hpp)。 - 互斥量(Mutex Lock):依赖操作系统提供的互斥量(如Linux的
pthread_mutex),涉及用户态到内核态的切换,开销大。 - 阻塞与唤醒:竞争失败的线程进入
_EntryList队列,通过park()挂起;锁释放时,唤醒队列中的线程。
代码片段(JVM层伪代码):
// ObjectMonitor.hpp
void ObjectMonitor::enter() {
if (CAS(&_owner, NULL, Self)) { // 尝试快速获取锁
return;
}
while (true) {
AddWaiterToEntryList(Self); // 加入阻塞队列
Self->_ParkEvent->park(); // 挂起线程(系统调用)
if (TryAcquire(Self)) break; // 被唤醒后重试获取锁
}
}
性能问题:
- 上下文切换:每次挂起/唤醒线程需切换CPU模式,耗时约数微秒至毫秒级。
- 无法自适应:即使竞争短暂,仍可能直接升级为重量级锁。
二、ReentrantLock与AQS:灵活并发的基石
1. ReentrantLock核心特性
- 可重入:同一线程可重复获取锁。
- 公平性:支持公平锁(按等待顺序获取)与非公平锁(插队抢锁)。
- 可中断:
lockInterruptibly()允许在等待时响应中断。 - 超时机制:
tryLock(long timeout, TimeUnit unit)。
2. AQS(AbstractQueuedSynchronizer)
形象理解:
AQS像一个排队管理器,负责协调线程对共享资源的访问。其核心是一个双向队列(CLH变体),线程通过“取号排队-等待通知”机制获取资源。
内部结构:
- state:
volatile int,表示资源状态(如ReentrantLock中表示持有锁的次数)。 - Node队列:等待线程封装为
Node节点,组成FIFO队列。
Node节点关键字段:
static final class Node {
volatile int waitStatus; // 状态:CANCELLED(1)、SIGNAL(-1)等
volatile Node prev; // 前驱节点
volatile Node next; // 后继节点
volatile Thread thread; // 关联线程
}
3. AQS工作流程(以ReentrantLock为例)
非公平锁加锁流程:
final void lock() {
if (compareAndSetState(0, 1)) // 直接尝试CAS抢锁
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1); // 进入AQS队列
}
public final void acquire(int arg) {
if (!tryAcquire(arg) && // 子类实现(再次尝试获取锁)
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
关键方法解析:
-
tryAcquire()(ReentrantLock实现):
protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (!hasQueuedPredecessors() && // 公平锁检查是否有前驱节点 compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { // 重入 setState(c + acquires); return true; } return false; } -
addWaiter():将线程包装为Node并加入队列尾部。
-
acquireQueued():自旋检查前驱节点是否为头节点,是则尝试获取锁;否则调用
LockSupport.park()挂起。
4. AQS的唤醒机制
- 释放锁:调用
unparkSuccessor(Node node),唤醒后继节点线程。 - 超时/中断:通过
cancelAcquire(Node node)将节点状态标记为CANCELLED,并调整队列。
三、Synchronized vs ReentrantLock
| 特性 | Synchronized | ReentrantLock |
|---|---|---|
| 实现方式 | JVM内置,自动管理锁 | JDK代码,需手动lock()/unlock() |
| 锁类型 | 非公平锁 | 支持公平锁与非公平锁 |
| 中断响应 | 不支持 | 支持lockInterruptibly() |
| 条件变量 | 通过wait()/notify() | 支持多个Condition |
| 性能(低竞争) | 优(锁升级优化) | 略优(CAS无阻塞) |
| 性能(高竞争) | 差(重量级锁开销) | 优(可配置自旋策略) |
四、总结与适用场景
- Synchronized:适合简单同步场景,代码简洁,JVM自动优化。
- ReentrantLock:需精细控制(如超时、公平性)、高并发竞争时性能更佳。
- AQS:作为并发工具基础框架(如Semaphore、CountDownLatch),理解其原理是掌握Java并发的关键。
代码启示:通过AQS的state和队列管理,Java实现了灵活高效的同步机制,而Synchronized的锁升级则体现了JVM对性能的极致优化。