一、ReentrantLock 特点
ReentrantLock 是 Java 并发包中提供的 可重入互斥锁,相比 synchronized
关键字,它提供了更灵活的锁控制机制,适用于复杂并发场景。
1. 可重入性
- 定义:同一线程可重复获取同一把锁,避免自身死锁。
- 实现:通过
state
变量记录锁的重入次数,每次释放锁时递减,直到state=0
完全释放锁 。
2. 锁模式(公平锁 vs 非公平锁)
- 公平锁:线程按请求顺序获取锁,避免饥饿,但性能较低(需维护队列)。
- 非公平锁(默认):允许线程插队获取锁,吞吐量更高,但可能导致某些线程长时间等待。
- 设置方式:通过构造函数指定(
new ReentrantLock(true)
为公平锁)。
3. 可中断性
- 作用:线程在等待锁的过程中可响应中断(通过
lockInterruptibly()
方法),避免无限阻塞 。
4. 锁超时机制
- 方法:
tryLock(long timeout, TimeUnit unit)
,在指定时间内尝试获取锁,超时返回false
,提升系统响应能力 。
5. 条件变量(Condition)
- 功能:替代
wait()
/notify()
,支持多路通知(一个锁绑定多个Condition
),实现更精准的线程唤醒 。
二、底层架构设计优势
ReentrantLock的性能优势核心来源于其 非公平锁实现 和 AQS(AbstractQueuedSynchronizer)机制,具体原理如下:
1. CAS原子操作优化
- 快速尝试机制:非公平锁(默认模式)在加锁时直接通过 CAS(Compare and Swap) 修改AQS的
state
值,无需检查等待队列,减少线程切换开销。
// NonfairSync.lock() 核心逻辑
final void lock() {{
if (compareAndSetState(0, 1)) // 直接CAS抢锁
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}}
- 避免内核态切换:CAS属于 用户态轻量级操作,而
synchronized
在竞争激烈时会升级为重量级锁,涉及 内核态线程调度(上下文切换耗时约1μs~1ms)。
2. 非公平锁的吞吐量优势
- 插队机制:新线程无需进入等待队列,可直接尝试获取锁。
- 适用场景:当锁释放的瞬间,新请求的线程可能比队列中的线程更快获得锁。
- 性能数据:非公平锁吞吐量比公平锁高 5~10倍(测试案例:100线程竞争)。
- 减少唤醒开销:公平锁必须按顺序唤醒队列线程,而非公平锁允许竞争,减少线程唤醒次数。
3. AQS队列管理优化
- CLH队列变种:AQS使用 双向链表(FIFO) 管理等待线程,但仅唤醒头节点后续线程。
// AbstractQueuedSynchronizer.acquireQueued()
final boolean acquireQueued(final Node node, int arg) {{
// 仅唤醒头节点的下一个节点
if (p == head && tryAcquire(arg)) {{
setHead(node);
return true;
}}
}}
- 自旋优化:线程加入队列前会短时自旋尝试获取锁,减少上下文切换。
三、基础使用方式对比
1. synchronized示例
// 修饰代码块(基于对象锁)
public class SyncExample {
private final Object lock = new Object();
private int counter = 0;
public void increment() {
synchronized (lock) { // 显式指定锁对象
counter++;
}
}
// 修饰方法(锁对象为this)
public synchronized void decrement() {
counter--;
}
}
2. ReentrantLock示例
import java.util.concurrent.locks.ReentrantLock;
public class LockExample {
private final ReentrantLock lock = new ReentrantLock();
private int counter = 0;
public void increment() {
lock.lock(); // 显式加锁
try {
counter++;
} finally {
lock.unlock(); // 必须手动释放
}
}
}
四、核心功能代码差异
1. 可中断锁获取
// ReentrantLock支持可中断锁
public void interruptibleLock() throws InterruptedException {
ReentrantLock lock = new ReentrantLock();
try {
lock.lockInterruptibly(); // 可响应中断的锁获取
// 业务代码
} finally {
lock.unlock();
}
}
// synchronized无法实现可中断锁
public synchronized void syncMethod() {
// 若其他线程持有锁,当前线程会无限阻塞
}
2. 超时锁获取
// ReentrantLock支持超时机制
public boolean tryLockWithTimeout() throws InterruptedException {
ReentrantLock lock = new ReentrantLock();
if (lock.tryLock(3, TimeUnit.SECONDS)) { // 等待3秒
try {
// 业务代码
return true;
} finally {
lock.unlock();
}
}
return false;
}
五、性能优化对比
1. 低竞争场景
- synchronized更优:JVM对
synchronized
有 偏向锁优化(无竞争时直接进入临界区,耗时约5ns)。 - ReentrantLock劣势:即使无竞争,仍需执行
lock()
和unlock()
的CAS操作(耗时约10ns)。
2. 高竞争场景
-
ReentrantLock优势明显:
指标 ReentrantLock(非公平) synchronized(重量级锁) 吞吐量(ops/ms) 1200 800 上下文切换次数/秒 200 1500 平均延迟(μs) 50 120
3. 减少锁粒度
- 分段锁:使用多个ReentrantLock实例分割数据块。
// 分段锁示例
private final ReentrantLock[] segmentLocks = new ReentrantLock[16];
public void update(int key) {{
int index = key % 16;
segmentLocks[index].lock();
try {{ /* 操作数据 */ }}
finally {{ segmentLocks[index].unlock(); }}
}}
4. 避免公平锁
- 性能损耗:公平锁吞吐量通常比非公平锁低 60%~70%(测试案例:50线程循环加锁)。
5. 条件变量精准唤醒
- 减少无效唤醒:使用
Condition
替代Object.wait()
,避免唤醒无关线程。
private final Condition notEmpty = lock.newCondition();
public void take() throws InterruptedException {{
lock.lock();
try {{
while (count == 0) notEmpty.await(); // 精准阻塞
// 取数据
}} finally {{ lock.unlock(); }}
}}
六、高级功能代码实现
1. 公平锁实现
// ReentrantLock公平锁(按请求顺序分配锁)
ReentrantLock fairLock = new ReentrantLock(true);
// synchronized无法实现公平锁
2. 多条件变量
// ReentrantLock可绑定多个Condition
public class ConditionDemo {
private final ReentrantLock lock = new ReentrantLock();
private final Condition notEmpty = lock.newCondition();
private final Condition notFull = lock.newCondition();
private Queue<Integer> queue = new LinkedList<>();
private int capacity = 10;
public void put(int value) throws InterruptedException {
lock.lock();
try {
while (queue.size() == capacity) {
notFull.await(); // 等待"非满"条件
}
queue.add(value);
notEmpty.signal(); // 唤醒"非空"等待线程
} finally {
lock.unlock();
}
}
}
七、特性对比表
特性 | synchronized | ReentrantLock |
---|---|---|
锁获取方式 | JVM隐式管理 | 显式调用lock() /unlock() |
可中断性 | ❌ 不可中断 | ✅ lockInterruptibly() |
公平锁 | ❌ 仅非公平 | ✅ 构造参数指定 |
超时机制 | ❌ | ✅ tryLock(timeout, unit) |
条件变量 | 单一wait() /notify() | ✅ 多Condition |
锁释放 | 自动释放(代码块结束/异常) | 必须finally 中手动释放 |
性能 | JDK6后优化较好 | 高竞争场景更优(非公平模式) |
八、选择建议(附场景示例)
1. 优先使用synchronized的场景
// 简单的线程安全计数器
public class SimpleCounter {
private int count = 0;
public synchronized void add() {
count++;
}
}
2. 必须使用ReentrantLock的场景
// 需要尝试获取锁的转账系统
public class BankTransfer {
private final ReentrantLock lock = new ReentrantLock();
public boolean transfer(Account from, Account to, int amount) {
if (lock.tryLock()) { // 避免死锁
try {
// 复杂的转账逻辑
return true;
} finally {
lock.unlock();
}
}
return false;
}
}
九、注意事项
1. 锁泄漏防护
// 错误示例(忘记unlock)
lock.lock();
try {
// 业务代码(若此处抛出异常,锁无法释放)
} finally {
// 必须在此释放!
}
2. 不要混用两种锁
// 错误示例(导致监控混乱)
synchronized(obj) {
reentrantLock.lock(); // 容易引发死锁
// ...
}
通过具体代码示例可以清晰看出,ReentrantLock
在复杂并发场景下具有更强的控制能力,而synchronized
在简单场景中更为简洁高效。建议根据具体需求选择最合适的同步机制 。