ReentrantLock源码解析
1.整体架构
类注释
ReentrantLock是可重入互斥锁,是一种排它锁。
可重入是指一个线程可以对一个共享资源重复加锁或释放锁,即当前线程获取该锁再次获取不会被阻塞。
其构造方法支持构建公平锁和非公平锁。公平性,是针对获取锁而言的,如果一个锁是公平的,那么锁的获取顺序就应该符合请求上的绝对时间顺序,满足FIFO。
- 公平锁每次获取到锁为同步队列中的第一个节点,保证请求资源时间上的绝对顺序,而非公平锁有可能刚释放锁的线程下次继续获取该锁,则有可能导致其他线程永远无法获取到锁,造成“饥饿”现象。
- 公平锁为了保证时间上的绝对顺序,需要频繁的上下文切换,而非公平锁会降低一定的上下文切换,降低性能开销。因此,ReentrantLock默认选择的是非公平锁,则是为了减少一部分上下文切换,保证了系统更大的吞吐量。
类结构
ReentrantLock本身不是AQS的子类,而是实现了 Lock接口,
public class ReentrantLock implements Lock, java.io.Serializable
Lock接口中定义了各种加锁的方法,
// 获得锁方法,获取不到锁的线程会到同步队列中
void lock();
// 获取可中断的锁
void lockInterruptibly() throws InterruptedException;
// 尝试获得锁,如果锁空闲,立马返回 true,否则返回 false
boolean tryLock();
// 带有超时等待时间的锁,如果超时时间到了,仍然没有获得锁,返回 false
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
// 释放锁
void unlock();
// 创建新的条件队列
Condition newCondition();
ReentrantLock中实现了上面的这些方法,这些方法的底层实际上是交给ReentrantLock中的内部类 Sync实现的,Sync是AQS的子类,
abstract static class Sync extends AbstractQueuedSynchronizer
Sync 继承了 AQS ,所以 Sync 就具有了锁的框架。根据 AQS 的框架,Sync 只需要实现 AQS 预留的几个方法即可,但 Sync 也只是实现了部分方法,还有一些交给子类 NonfairSync 和 FairSync 去实现。NonfairSync 是非公平锁,FairSync 是公平锁,定义如下,
static final class FairSync extends Sync
static final class NonfairSync extends Sync
上述几个类的实现结构如下,
构造方法
ReentrantLock存在两种构造方法,
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
无参数时默认创建非公平锁,有参时可以指定创建公平锁和非公平锁。创建的同步器对象被ReentrantLock的sync
成员变量引用。
2.源码解析
Sync同步器
Sync同步器是AQS的子类,该类依旧是一个抽象类,
lock
方法是一个抽象方法,留给 FairSync和 NonfairSync去实现。
1)重入性实现原理—nonfairTryAcquire方法
该方法是Sync中定义的关键方法,实现了可重入锁的重入性,同时也用于获取非公平锁,
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 同步器的状态是 0,表示同步器的锁没有线程持有
// 如果该锁未被任何线程占有,该锁能被当前线程获取,返回true
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 若被占有,检查占有线程是否是当前线程
else if (current == getExclusiveOwnerThread()) {
// 再次获取,计数加一
int nextc = c + acquires;
// int 是有最大值的,<0 表示持有锁的数量超过了 int 的最大值
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
- 通过判断 AQS 的
state
的状态来决定是否可以获得锁,0 表示锁是空闲的 - else if 的代码体现了可重入加锁,同一个线程对共享资源重入加锁,底层实现就是把
state + 1
,并且可重入的次数是有限制的,为 Integer 的最大值 - 这个方法是非公平的,所以只有非公平锁才会用到,公平锁是另外的实现
返回true
表示获取锁成功;反之,表示获取锁失败继续留在同步队列中。
2)释放锁—tryRelease方法
释放锁方法公平锁和非公平锁都适用,
protected final boolean tryRelease(int releases) {
//1. 同步状态减1
int c = getState() - releases;
// 如果当前线程不持有锁,报错
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//2. 只有当同步状态为0时,才说明锁成功被释放,返回true
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
// 3. 锁未被完全释放,返回false
setState(c);
return free;
}
从代码中可以看到,锁最终被释放的标椎是 state
的状态为 0。在重入加锁的情况下,需要重入解锁相应的次数后,才能最终把锁释放,比如线程 A 对共享资源 B 重入加锁 5 次,那么释放锁的话,也需要释放 5 次之后,才算真正的释放该共享资源了。
公平锁—FairSync
FairSync在Sync的基础上实现了lock
和tryAcquire
方法
1)资源加上公平锁—lock方法
代码十分简单,
final void lock() {
acquire(1);
}
acquire
方法是 AQS 的方法,表示先尝试获得锁,失败之后进入同步队列阻塞等待。
回顾AQS的acquire
方法,
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
其中acquireQueued
和addWaiter
方法是AQS实现的具体的作用参考笔记。
2)公平锁处理逻辑—tryAcquire方法
FairSync的父类Sync中的nonfairTryAcquire
方法只是简单获取了同步器的状态并依此做了一些处理使得当前线程获取锁。但是该方法未考虑到节点在同步队列中的排序。
公平锁的tryAcquire
方法对同步队列顺序的处理如下,
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// hasQueuedPredecessors 是实现公平的关键
// 会判断当前线程之前是否还有前置节点,且该前置节点不是头节点
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;
}
}
这段代码的逻辑与Sync#nonfairTryAcquire
基本上一直,唯一的不同在于增加了hasQueuedPredecessors
用来判断当前节点在同步队列中是否有前驱节点。公平锁每次都是从同步队列中的第一个节点获取到锁,而非公平性锁则不一定,有可能刚释放锁的线程能再次获取到锁。
非公平锁—NonfairSync
NonfairSync在Sync的基础上也实现了lock
和tryAcquire
方法,
1)资源加上非公平锁—lock方法
final void lock() {
// cas 给 state 赋值
if (compareAndSetState(0, 1))
// cas 赋值成功,代表拿到当前锁,记录拿到锁的线程
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
2)非公平锁处理逻辑—tryAcquire方法
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
直接调用父类 Sync的nonfairTryAcquire
方法。
ReentrantLock源码
1)加可重入锁—lock方法
public void lock() {
sync.lock();
}
调用成员变量sync
的lock方法,依据初始化时创建的锁,对资源进行上锁操作,
2)尝试加锁—tryLock方法
tryLock
方法有两种,无参和有参,
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
// timeout 为超时的时间,在时间内,仍没有得到锁,会返回 false
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
-
无参
tryLock
的调用关系图如下,
无参方法底层走的就是非公平锁实现,没有公平锁的实现。 -
有参的
tryLock
的调用如下,
3)释放锁—unlock方法
unlock
方法源码如下,
public void unlock() {
sync.release(1);
}
上方代码中,release
方法是AQS的方法,调用了子类 Sync的tryRelease
方法,该方法的详细说明见笔记。
release
方法分为两步,
tryRelease
方法尝试释放锁,如果失败直接返回 false- 释放成功,则进一步唤醒同步队列头节点的后继节点,让该节点去争锁
4)创建条件队列—newCondition方法
public Condition newCondition() {
return sync.newCondition();
}
调用 AQS的newCondition
方法,返回ConditionObject类对象。