Lock(四) — 可重入锁(ReentrantLock)源码解析
程序员文章站
2022-05-05 23:13:56
...
ReentrantLock源码解析
一、概述
重入锁: 当该锁被线程A持有时,线程A仍可以重新获取该锁。
公平锁: 在获取锁时,如果线程A先对锁进行获取,则当锁释放后,线程A优先获得锁。
非公平锁: 在获取锁时,如果线程A先对锁进行获取,则当锁释放后,线程A不一定先得到锁。
通常情况下,非公平锁效率比公平锁效率高。
- 在获取公平锁时,如果同步队列内已经有等待的线程时,那么会优先给同步队列内的线程,而此时请求获取锁的线程就进入同步队列等待,存在线程切换开销。
- 在获取非公平锁时,会优先给当前请求获取锁的线程机会,如果获取锁成功就直接返回(降低了线程切换的开销),否则将当前获取锁的线程加入同步队列进行等待。
二、ReentrantLock 类图
ReentrantLock 类图
上图是 ReentrantLock 类图,从图中可知:
-
ReentrantLock
实现了 Lock 接口,因此具备了锁的相关操作。 -
ReentrantLock
内部包含一个继承了AbstractQueuedSynchronizer
的Sync
同步器,因此所有用户态的 Lock 操作都委托给 Sync 来执行。 -
Sync
有两个子类:FairSync
、NonfairSync
,他们实现了锁获取策略的公平性问题。 - 在 ReentrantLock 中使用到了 AQS ,可以参考:
三、源码解析
1. ReentrantLock 的使用
获取锁和释放锁的操作使用 before...after
模式,且获取锁的操作不放在 try
中。
ReentrantLock lock = new ReentrantLock();
lock.lock(); // before
try {
// 执行逻辑操作
} finally {
lock.unlock(); //after
}
2 ReentrantLock 的构造
默认使用的是非公平锁的策略
// 默认的 ReentrantLock 使用的是非公平锁的策略
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
3. ReentrantLock 锁的获取
在获取锁时,会调用到 AQS 的 acquire()
方法。该方法的具体解析请参考文章: Lock(二) — 队列同步器(AQS)浅析
3.1 非公平锁的获取
// ReentrantLock.class
public void lock() {
sync.lock(); // sync分公平和非公平锁两种
}
// ReentrantLock.NonfairSync.class
final void lock() {
// 非公平锁:让当前获取锁的线程优先尝试获取锁操作(这里第1次)
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
// 执行到这里说明上面获取锁没有成功
acquire(1); //acquire(1)方法是AQS内部的
}
// AbstractQueuedSynchronizer.class
public final void acquire(int arg) {
/*
* 分2步进行判断:
* 1.tryAcquire(arg):尝试获取锁(获取锁成功,则直接返回)
* 2.addWaiter(Node.EXCLUSIVE):当tryAcquire(arg)获取锁失败后,则使当前线程进入同步队列等待。
* 3.acquireQueued(node, arg):使当前获取锁的线程进入自旋,循环判断是否可以获取到锁,并返回是否中断该线程的标记。
* 说明:acquireQueued()方法内部不响应线程的中断操作,所以将是否中断操作抛出,在这里执行。
*/
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
//如果返回中断,则调用当前线程的interrupt()方法
selfInterrupt();
}
// ReentrantLock.NonfairSync.class
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires); //非公平锁的获取
}
// ReentrantLock.Sync.class
// 主要分为下面3个步骤
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState(); //1.获取锁是否已经被其他线程持有(0表示该锁没有被线程持有)。
if (c == 0) { //2.当c==0时,表示该锁没有线程持有
/*
* 2.1 这里存在多个线程同时操作的问题,所以得通过CAS原子操作更新state状态。
* 一旦state更新成功,则其他线程在执行到c==0时或通过CAS再次更新state状态时,就会返回false。
*/
if (compareAndSetState(0, acquires)) { // (非公平锁:这里第2次尝试让当前线程获取锁)
// 2.2 将当前持有锁的线程保存到AQS的变量中。
setExclusiveOwnerThread(current);
return true;
}
}
/*
* 3.执行到这里,表明锁被线程持有(该线程可能是持有锁的线程,也可能是其他线程)
* 这里判断当前的线程跟持有锁的线程是否是相同,如果相同,就再次获取锁成功(锁的可重入性);
*/
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires; // 3.1 这里增加state表示重入次数。
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc); //3.2 这里由于当前线程跟持有锁的线程相同,所以不存在竞争,因此不需要通过CAS操作更新state状态。
return true;
}
return false;
}
3.2 公平锁的获取
// ReentrantLock.class
public void lock() {
sync.lock(); // sync分公平和非公平锁两种
}
// ReentrantLock.FairSync.class
final void lock() {
acquire(1); //acquire(1)方法是AQS内部的
}
// AbstractQueuedSynchronizer.class
public final void acquire(int arg) {
// 该方法在非公平锁获取时已经阐述,此处不解释。
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
// ReentrantLock.FairSync.class
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState(); //1.获取锁是否已经被其他线程持有(0表示该锁没有被线程持有)。
if (c == 0) {//2.当c==0时,表示该锁没有线程持有。
/*
* 2.1 hasQueuedPredecessors():表示判断同步队列中是否有其他线程在等待获取锁(true=同步队列有其他线程在等待)。
* 说明:这个方法可以保证获取锁的公平性,先到先得,后来的线程在同步队列等待(队列特点:FIFO)。
*/
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) { //2.2 通过CAS操作更新state状态。
setExclusiveOwnerThread(current); //2.3 将当前持有锁的线程保存到AQS的变量中。
return true;
}
}
/*
* 3.执行到这里,表明锁被线程持有(该线程可能是持有锁的线程,也可能是其他线程)
* 这里判断当前的线程跟持有锁的线程是否是相同,如果相同,就再次获取锁成功(锁的可重入性);
*/
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc); //更新状态 state表示线程重入次数。
return true;
}
return false;
}
4. ReentrantLock 锁的释放
// ReentrantLock.class
public void unlock() {
sync.release(1);
}
// AbstractQueuedSynchronizer.class
public final boolean release(int arg) {
if (tryRelease(arg)) { //尝试释放锁
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h); // 此处尝试释放节点
return true;
}
return false;
}
// ReentrantLock.Sync.class
protected final boolean tryRelease(int releases) {
int c = getState() - releases; //1.这里每释放一次,state就要减小releases数量(可重入性)
// 2.这里判断释放锁的线程是否是当前线程
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) { //3.当state==0,说明该锁没有被任何线程持有
free = true;
setExclusiveOwnerThread(null); //4.将独占锁关联的线程信息移除
}
setState(c); //因为第2步已经判断是否是同一个线程,所以这里的操作是线程安全的。
return free;
}
四、小结
1. 如何实现公平锁?
在获取锁的时候,通过 hasQueuedPredecessors()
方法判断同步队列内 是否存在 其他线程也在等待获取锁资源。
- 若存在: 这说明有比当前线程早的,为了公平性,则需要让他们先获得锁,而将自己添加进入同步队列进行等待。
- 若不存在: 说明当前线程可以直接获取锁资源,当前线程是最先获取锁的。
2. 如何实现非公平锁?
非公平锁提供了两次优先于同步队列获取锁的机会。
第一次: 在调用 lock()
方法时,先尝试让当前线程直接通过 compareAndSetState(0, 1)
(CAS)操作来更新 state 状态,更新成功则说明当前线程获取到锁。
第二次: 在第一次没有获取到锁时,在 tryAcquire()
方法中,再次尝试让当前线程直接通过 compareAndSetState(0, 1)
(CAS)操作来更新 state 状态,更新成功则说明当前线程获取到锁。
两次都没有成,则将当前线程放入同步队列等待。
注意:
- 不管是共配锁,还是非公平锁,同步队列获取锁都是有先后顺序的。
- 非公平锁只是给 当前线程 提供了2次优先竞争锁的机会。