一、ReentrantLock 的锁类型
ReentrantLock 内部通过 Sync 类(继承自 AbstractQueuedSynchronizer)实现锁机制,其子类 FairSync(公平锁)和 NonfairSync(非公平锁)分别对应两种模式:
// ReentrantLock 构造函数(默认非公平锁)
public ReentrantLock() {
sync = new NonfairSync();
}
// 指定公平性的构造函数
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
二、非公平锁(NonfairSync)的实现
1. lock() 方法
非公平锁在尝试获取锁时,直接通过 CAS 抢占资源,不检查等待队列:
final void lock() {
if (compareAndSetState(0, 1)) // 直接尝试 CAS 抢锁
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1); // 进入 AQS 队列等待
}
2. tryAcquire() 方法
在 nonfairTryAcquire 中,直接尝试修改 state,无视队列中的等待线程:
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)) { // 无视队列,直接 CAS 抢锁
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;
}
关键点:
抢占式获取:直接 CAS 尝试获取锁,不检查队列是否有等待线程。
高吞吐量:减少线程切换,但可能导致饥饿。
3. tryAcquire() 方法和 lock() 方法的主要区别如下
功能与行为:
lock() 方法是一个阻塞式的加锁方法。
如果锁已经被其他线程持有,调用 lock() 的线程会一直等待,直到获取到锁为止。
tryAcquire() 方法是一个非阻塞式的尝试加锁方法。
如果锁未被持有,它会尝试获取锁并返回 true;如果锁已被持有,它会立即返回 false,而不会阻塞线程。
实现机制:
lock() 方法内部会调用 acquire() 方法,而 acquire() 方法会进一步调用 tryAcquire() 来尝试获取锁。
如果 tryAcquire() 失败,线程会被加入到等待队列中,进入阻塞状态。
tryAcquire() 是 ReentrantLock 内部实现的核心方法,具体逻辑由公平锁(FairSync)
和非公平锁(NonfairSync)分别实现。公平锁会严格按照等待队列的顺序获取锁,而非公平锁则允许插队。
使用场景:
lock() 方法适用于需要确保线程一定能够获取到锁的场景,即使需要等待。
tryAcquire() 方法适用于需要尝试获取锁但不希望线程阻塞的场景,例如在尝试获取锁失败时执行其他逻辑。
超时机制:
lock() 方法没有超时机制,线程会一直等待直到获取锁。
tryAcquire() 可以通过 tryAcquireNanos() 方法实现带超时的尝试获取锁,允许线程在指定时间内等待锁。
总结来说,lock() 是阻塞式的加锁方法,适合必须获取锁的场景;而 tryAcquire() 是非阻塞式的尝试加锁方法,适合需要灵活处理锁获取失败的场景。
三、公平锁(FairSync)的实现
lock() 方法
公平锁直接调用 acquire(),进入队列等待:final void lock() { acquire(1); // 直接进入 AQS 队列排队 }
tryAcquire() 方法
在尝试获取锁前,先检查队列中是否有等待线程(hasQueuedPredecessors()):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; }
关键点:
队列检查:hasQueuedPredecessors() 确保只有队列为空或当前线程是队列头节点时,才能获取锁。
公平性保证:严格遵循 FIFO 顺序,避免饥饿。
四、核心方法:hasQueuedPredecessors()
检查队列中是否有优先级更高的等待线程:
public final boolean hasQueuedPredecessors() {
Node h, s;
if ((h = head) != null) {
if ((s = h.next) == null || s.waitStatus > 0) {
s = null; // 遍历队列找到第一个有效节点
for (Node p = tail; p != h && p != null; p = p.prev) {
if (p.waitStatus <= 0)
s = p;
}
}
if (s != null && s.thread != Thread.currentThread())
return true; // 存在其他等待线程
}
return false;
}
五、对比总结
特性 非公平锁(NonfairSync) 公平锁(FairSync)
锁获取策略 直接 CAS 抢占,无视队列 先检查队列,仅当队列无等待时获取
吞吐量 高(减少线程切换) 较低(频繁上下文切换)
饥饿问题 可能发生饥饿 避免饥饿
适用场景 高并发且线程持有锁时间短的场景 要求公平性的场景(如任务调度)
六、源码设计思想
- 模板方法模式:AQS 提供骨架方法(如 acquire()),子类重写 tryAcquire() 实现具体逻辑。
- 状态管理:通过 state 变量实现锁的重入计数和许可证管理。
- 队列管理:CLH 队列变种高效处理线程排队与唤醒。
七、ReentrantLock 与 ReentrantReadWriteLock 的区别
锁的类型:
ReentrantLock 是一种互斥锁,无论是读操作还是写操作,都需要独占锁资源。
ReentrantReadWriteLock 是一种读写锁,将锁分为读锁和写锁,读锁可以共享,写锁是独占的。
锁的粒度:
ReentrantLock 的锁粒度较粗,读读、读写、写写操作都会互斥。
ReentrantReadWriteLock 的锁粒度更细,支持读读共享,读写和写写互斥,适合读多写少的场景。
性能:
ReentrantLock 在高并发读场景下性能较低,因为所有操作都需要互斥。
ReentrantReadWriteLock 在读多写少的场景下性能更高,因为读操作可以并发执行。
锁降级:
ReentrantLock 不支持锁降级。
ReentrantReadWriteLock 支持锁降级,即写锁可以降级为读锁,保证数据可见性。
使用场景:
ReentrantLock 适合需要独占锁的场景,如对共享资源的写操作。
ReentrantReadWriteLock 适合读多写少的场景,如缓存系统。
总结
ReentrantLock 是一种通用的互斥锁,适合需要独占锁的场景。
ReentrantReadWriteLock 通过分离读锁和写锁,提高了读操作的并发性能,适合读多写少的场景
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。