ReentrantLock源码详解
前言
以前只知道ReentrantLock底层基于AQS实现,相对于(旧版本的)synchronized:
- 更轻量(基于CAS而不是管程),由JDK实现
- 可以实现公平/非公平
- 可中断等待
- 可绑定多个条件,以选择性地通知其他进程解除等待。
那在我们分析ReentrantLock源码之前,首先了解一下ReentrantLock的工作流程:
此图转载自https://blog.csdn.net/qq_27184497
然后看一下上述features是如何实现的。
类定义、构造
public class ReentrantLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = 7373984872572414699L;
/** Synchronizer providing all implementation mechanics */
private final Sync sync;
abstract static class Sync extends AbstractQueuedSynchronizer {
主要实现了Lock接口,规范了一个lock应当具有的基本功能:上锁、解锁、tryLock等。
并且有一个内部抽象类和属性Sync继承自AQS。
构造函数:
public ReentrantLock() {sync = new NonfairSync();}
public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();}
默认实现非公平锁,可指定实现公平锁。公平/非公平锁的大部分方法都相同,不同的方法在Sync类中定义为抽象方法,由公平/非公平两个子类实现,我们先以非公平锁的实现为例,一会再对比非公平锁。
去看看ReentrantLock主要使用流程的方法们是怎样的。
Lock方法:
public void lock() {sync.lock();}
调用了sync的方法,继续深入:
final void lock() {
if (!initialTryLock())
acquire(1);
}
看一下initialTryLock方法:
final boolean initialTryLock() {
Thread current = Thread.currentThread();
if (compareAndSetState(0, 1)) { // first attempt is unguarded
setExclusiveOwnerThread(current);
return true;
} else if (getExclusiveOwnerThread() == current) {
int c = getState() + 1;
if (c < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(c);
return true;
} else
return false;
}
首先创建一个线程Thread,然后尝试SetState,
- 如果成功,将该线程设为独占线程(此方法来自于AQS)
- 如果失败,那么获取当前独占线程是否为这个current,我理解这里是考虑到可重入场景。(这里state相关函数都是AQS的方法,改天再开一篇博客详解AQS)在这个可重入场景下,变量c的逻辑是为了保证重入多少次,就要unlock多少次。
- 如果当前独占线程不是这个current,说明上锁失败。
在lock方法中,若该方法return false,则进入acquire(1),
public final void acquire(int arg) {
if (!tryAcquire(arg))
acquire(null, arg, false, false, false, 0L);
}
里面有很多很多参数的这个方法是AQS的方法,公平/非公平、等待时长、中断等最终都是这个方法实现,只不过他们的tryAcquire方法不一样,对于非公平锁而言大概就是自旋不断CAS尝试获取,这是公平/非公平的主要区别,公平锁会进入等待队列,而非公平直接自旋抢占。
lockInterruptibly()方法,可被中断地上锁,和Lock方法基本类似:
final void lockInterruptibly() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (!initialTryLock())
acquireInterruptibly(1);
}
public final void acquireInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted() ||
(!tryAcquire(arg) && acquire(null, arg, false, true, false, 0L) < 0))
throw new InterruptedException();
}
下面这个方法来自AQS。如果获取锁之前就中断,那么直接抛出InterruptedException异常。
如果获取锁失败,那么和Lock方法的区别就是自旋前会调用Thread.interrupted()方法,确认当前线程是否被调用了interrupt,如果中断,那么抛出InterruptedException。
基本逻辑就是若两个线程都使用lockInterruptibly获取锁,如果线程A获取到了锁,线程B只能等待,对线程B调用interrupt()方法能够中断线程B的等待过程
tryLock方法:
同样来自于内部的Sync类,tryLock方法有两个,第一个和Lock方法调用的initialTryLock方法基本一样,唯一的区别就是这个方法首先会获取状态,而不是一上来直接CAS尝试获取锁,这里不做分析了,第二个是带等待时长参数的,我们直接进去看:
final boolean tryLockNanos(long nanos) throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
return initialTryLock() || tryAcquireNanos(1, nanos);
}
return的左值很好理解,就是没有经过时长等待就获取到了锁,根据||的短路效应,就没有必要进入右边了。
而右边又是一个AQS方法:
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (!Thread.interrupted()) {
if (tryAcquire(arg))
return true;
if (nanosTimeout <= 0L)
return false;
int stat = acquire(null, arg, false, true, true,
System.nanoTime() + nanosTimeout);
if (stat > 0)
return true;
if (stat == 0)
return false;
}
throw new InterruptedException();
}
大概就是如果在等待时长(nanos参数)内获取到了锁,那么就返回true,如果超时了,那么放弃,返回false。
Unlock方法:
直接调用了AQS的release方法,其中arg是1:
public final boolean release(int arg) {
if (tryRelease(arg)) {
signalNext(head);
return true;
}
return false;
}
signalNext方法是唤醒等待队列中的队头线程,当然了只有公平锁有等待队列。
这个tryRelease方法在AQS中是个钩子方法,也就是交由Sync类实现,我们看一下:
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (getExclusiveOwnerThread() != Thread.currentThread())
throw new IllegalMonitorStateException();
boolean free = (c == 0);
if (free)
setExclusiveOwnerThread(null);
setState(c);
return free;
}
首先计算释放后的重入值c,arg是1(所以releases就是1),因此c就是state-1,如果调用释放的线程不是当前工作线程,那么抛出异常。
如果可重入值在释放之后将要变为0,那么说明该锁不被任何线程持有,那么将该锁的独占线程设为null,然后更新可重入值。
Condition
Condition类似于Synchronized中notify,wait等等,只不过更加灵活,一个Lock可以有多个Condition来执行各不相同的条件。
具体用法可以参考这篇文章:
https://blog.csdn.net/wugemao/article/details/83900078
其底层原理不属于ReentrantLock范畴,待日后详解AQS时一并分析。
公平锁的区别:
这里以公平锁的Lock方法为例,其他方法实现公平的方式与之基本一致,公平/非公平锁的Lock方法用得是同一个,但实现不同:
public final void acquire(int arg) {
if (!tryAcquire(arg))
acquire(null, arg, false, false, false, 0L);
}
调用的initialTryLock方法一些区别:
final boolean initialTryLock() {
Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedThreads() && compareAndSetState(0, 1)) {
setExclusiveOwnerThread(current);
return true;
}
} else if (getExclusiveOwnerThread() == current) {
if (++c < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(c);
return true;
}
return false;
}
可以看到和非公平锁的initialTryLock方法主要区别就是条件里多加了一个“有排队中的线程”判断,这样只有在当前线程重入,或没有等待线程(等待队列为空)时,才会获取该锁。而公平锁等待队列的实现本就是AQS的核心,因此待日后详解AQS时一并分析。
彩蛋:UID的作用?
源码中有一个属性:
private static final long serialVersionUID = 7373984872572414699L;
这个东西好像也没见到哪个方法里使用了,那它是干什么的?
中文环境中没有查到。。知识盲区了,我理解,大概,类似一种摘要算法生成的code?请大佬解释。。