一、ReentrantLock概述
ReentrantLock是Java并发包(java.util.concurrent.locks)中的一个重要组件,它是一个可重入的互斥锁,具有与synchronized关键字类似的功能,但提供了更灵活的锁操作。
基本特性
- 可重入性:同一个线程可以多次获取同一把锁
- 可中断:支持获取锁的过程中响应中断
- 公平性选择:支持公平锁和非公平锁两种模式
- 条件变量:支持多个条件队列
基本用法示例
java
ReentrantLock lock = new ReentrantLock();
public void criticalSection() {
lock.lock(); // 获取锁
try {
// 临界区代码
} finally {
lock.unlock(); // 释放锁
}
}
二、ReentrantLock的JVM层面实现
1. 核心类结构
ReentrantLock的实现主要依赖于以下几个核心类:
AbstractQueuedSynchronizer
(AQS):同步器框架基类NonfairSync
:非公平锁实现FairSync
:公平锁实现
java
public class ReentrantLock implements Lock, java.io.Serializable {
private final Sync sync;
abstract static class Sync extends AbstractQueuedSynchronizer {
// 实现AQS的核心方法
}
static final class NonfairSync extends Sync {
// 非公平锁实现
}
static final class FairSync extends Sync {
// 公平锁实现
}
}
2. AQS核心机制
AQS是ReentrantLock实现的核心,它维护了一个volatile int state变量和一个FIFO线程等待队列。
state变量含义
- 0:锁未被任何线程持有
0:锁被某个线程持有,数值表示重入次数
等待队列
- CLH队列的变种,用于实现线程的排队等待
- 每个节点代表一个等待线程
3. 加锁过程分析
非公平锁实现
java
final void lock() {
if (compareAndSetState(0, 1)) // 直接尝试CAS获取锁
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1); // 获取失败则进入AQS排队流程
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) { // 重入检查
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
公平锁实现
java
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()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
4. 解锁过程分析
java
public void unlock() {
sync.release(1);
}
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
三、JVM层面的关键实现细节
1. CAS操作
ReentrantLock大量使用了CAS(Compare-And-Swap)操作,这是通过Unsafe类实现的:
java
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
在JVM层面,CAS操作会被编译为特定平台的原子指令,如x86的cmpxchg
指令。
2. 内存可见性
AQS中的state变量使用volatile修饰,保证了内存可见性:
java
private volatile int state;
在JVM层面:
- 写volatile变量会插入StoreStore和StoreLoad内存屏障
- 读volatile变量会插入LoadLoad和LoadStore内存屏障
3. 线程阻塞与唤醒
当线程获取锁失败时,会被park(阻塞):
java
LockSupport.park(this);
在JVM层面,这最终会调用到操作系统的线程调度机制:
- Linux下使用pthread_cond_wait
- Windows下使用WaitForSingleObject
当锁释放时,会unpark(唤醒)等待线程:
java
LockSupport.unpark(node.thread);
四、性能优化分析
1. 非公平锁的性能优势
非公平锁之所以性能更好,是因为:
- 减少了线程切换的开销
- 利用了线程的局部性原理
- 避免了频繁的上下文切换
2. 自旋优化
在进入park前,AQS会进行有限次数的自旋尝试,减少线程挂起的开销。
五、与synchronized的对比
|-------|---------------|------------------|
| 特性 | ReentrantLock | synchronized |
| 实现层面 | Java代码实现 | JVM内置实现 |
| 锁获取方式 | 显式lock/unlock | 隐式通过monitor进入/退出 |
| 可中断性 | 支持 | 不支持 |
| 公平性 | 可配置公平/非公平 | 完全非公平 |
| 条件变量 | 支持多个Condition | 只有一个等待队列 |
| 性能 | JDK1.6+优化后接近 | JDK1.6+性能大幅提升 |
六、最佳实践
- 总是使用try-finally释放锁:
java
lock.lock();
try {
// 临界区代码
} finally {
lock.unlock();
}
- 合理选择公平策略:默认使用非公平锁,除非有特殊需求
- 避免锁泄漏:确保锁在finally块中释放
- 考虑使用tryLock:避免死锁
java
if (lock.tryLock(1, TimeUnit.SECONDS)) {
try {
// 获取锁成功
} finally {
lock.unlock();
}
} else {
// 获取锁超时处理
}
七、总结
ReentrantLock是Java并发编程中的重要工具,它通过AQS框架实现了高效的可重入锁机制。从JVM层面看,它充分利用了CAS操作、volatile变量和线程调度机制,在保证线程安全的同时提供了良好的性能。