ReentrantLock源码解析
一、简介
ReentrantLock是一个轻量级锁,主要采用同步队列,以及CAS去进行资源的争夺,包含一个重要的属性Sync,会依靠这个Sync来进行资源争夺,同时这个Sync是继承了AQS的,推荐看这个之前先看AQS
重要内部类
Sync
//重要,我们的ReentrantLock的加锁解锁基本靠这个
abstract static class Sync extends AbstractQueuedSynchronizer {
//加锁方法,被abstract修饰,需要子类实现
abstract void lock();
//不公平的尝试获取锁,final修饰,代表不能被重写
final boolean nonfairTryAcquire(){...}
//尝试释放占有的资源,被final修饰,不可重写,说明在释放state,公平锁和非公平锁都是一样的
protected final boolean tryRelease(int releases) {...}
Sync.nonfairAcquire()
final boolean nonfairTryAcquire(int acquires) {
//获取当前线程
final Thread current = Thread.currentThread();
//获取state变量的值,也就是当前锁的被重入的次数
int c = getState();
//state为0,说明当前锁未被任何线程持有
if (c == 0) {
//因为此时没有被占用,所以自己还去尝试占有锁
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//如果是自己占有的锁,因为是可重入锁
else if (current == getExclusiveOwnerThread()) {
//计算state需要更新的值
int nextc = c + acquires;
//
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
//更新
setState(nextc);
return true;
}
return false;
}
Sync.tryRelease()方法
protected final boolean tryRelease(int releases) {
//计算我们的state-我们需要释放的大小,代表释放成功后,我们的release需要CAS设置的值
int c = getState() - releases;
//如果不是自己的线程占有的,需要抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//如果释放自己的资源后,state是0的话,需要设置占有线程是null,方便其他的线程去占有资源
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
//修改state
setState(c);
//返回是否释放成功
return free;
}
}
上面介绍了Sync的全部方法,接下来看他的子类实现NonfairSync和FairSync两个,分别代表不公平和公平的,两者加锁流程会有不一样
NonfairSync
static final class NonfairSync extends ReentrantLock.Sync {
//非公平锁加锁流程,被final修饰,不可重写
final void lock() {...}
//非公平锁尝试获取锁的方法
protected final boolean tryAcquire(int acquires) {...}
}
NonfairSync.lock()
//非公平锁的加锁方式
final void lock() {
//直接尝试去CAS修改我们的state,这个就是体现非公平了,因为作为对比,公平锁在一开始并不会去尝试CAS修改我们的state
//公平锁一开始只会有一次尝试CAS,失败后是直接去加入队列,而非公平锁在一开始会尝试2次去占有锁,这里是第一次,之后还会有一次
if (compareAndSetState(0, 1))
//成功的话就会修改占有线程
setExclusiveOwnerThread(Thread.currentThread());
else
//调用到父类的父类,也就是AQS的acquire方法,AQS定义了一套获取锁的算法逻辑,具体实现交给子类实现,但用模板设计模式
//给出了大概思路,回忆一下,AQS.acquire会调用tryAcquire(),addWaiter(),acquireQueued()三个重要方法,
//tryAcquire是交给了子类实现
acquire(1);
}
//我们的非公平锁的tryAcquire()方法,调用我们的父类Sync.nonfairTryAcquire()去了
//在父类的方法里面会判断state是不是0,然后继续尝试CAS一次,所以在入队以前,非公平锁会有两次直接尝试获取的时间
//如果都失败了,就会开始进行入队操作,这个是在AQS的逻辑中了,建议先看完AQS再来看reentrantLock
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
上面就是非公平锁的加锁过程,释放过程是在我们的Sync中定义好了
这里就是我们的公平锁的逻辑了
FairSync
static final class FairSync extends ReentrantLock.Sync {
final void lock() {...}
protected final boolean tryAcquire(int acquires) {...}
}
FairSync.lock()
final void lock() {
//直接调用AQS.acquire(),非公平锁在这里会先进行一次CAS,公平锁不会
acquire(1);
}
FairSync.lock()
//公平锁的加锁逻辑,基本和非公平锁一样,不过多了一个地方!hasQueuedPredecessors(),也是不同
protected final boolean tryAcquire(int acquires) {
//获取到当前线程
final Thread current = Thread.currentThread();
//获取到state
int c = getState();
//如果没有人占领
if (c == 0) {
//尝试CAS修改
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;
}
//主要就是判断我们的队列的情况,看自己能不能进行一次直接CAS
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
//因为想要去继续CAS,这里必须返回false,如果满足了,就不能进行CAS
//如果头尾相等,也就是同步等待队列里面没有其他线程,返回的就是false,配合!,就是true,然后就可以去进行CAS我们的state
//因为只有自己,所以不需要去排队
//头尾不相等,代表已经有其他的下刹那哼,如果head的下一个节点是空的,或者这个节点不是自己的当前的这个线程,就会返回false
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
ReentrantLock
然后看我们的reentrantLock怎么使用这个加锁,以及加锁流程是怎样的
ReentrantLock.lock()加锁方法
public void lock() {
//直接调用sync.lock(),这个sync是什么时候
sync.lock();
}
ReentrantLock.unlock()
public void unlock() {
sync.release(1);
}
这个sync是什么时候初始化的呢
在构造函数进行初始化,默认是非公平的,支持自定义,传入true是公平锁,false是非公平锁
public ReentrantLock() {
sync = new ReentrantLock.NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new ReentrantLock.FairSync() : new ReentrantLock.NonfairSync();
}
总结
非公平锁:
- 调用reentrantLock.lock()
- 先去执行到我们的sync.lock()
- 尝试先去进行一次CAS占有锁,成功直接返回
- 失败就会调用AQS.acquire(),分别执行tryAcquire()尝试获取锁资源,addWaiter()添加入队列,acquireQueued()进行阻塞直到获取锁
- 先执行到nonFairSync.tryAcquire(),然后执行到Sync.nonfairTryAcquire()
- 判断state状态是不是为0,是的话继续进行一次CAS占有锁
- 判断占有线程是不是自己,是的话直接将需要的大小加到state上去
- 否则占有不成功,需要执行AQS的另外两个方法进入队列,然后等待获取锁
公平锁
- 调用reentrantLock.lock()
- 先去执行到我们的sync.lock()
- 直接调用AQS.acquire(),分别执行tryAcquire()尝试获取锁资源,addWaiter()添加入队列,acquireQueued()进行阻塞直到获取锁
- 调用fairAcquire.tryAcquire(),
- 然后先判断state是不是没人占领,是的话会先判断队列情况,例如是不是没有其他线程,占有线程是不是自己之类的
- 然后尝试CAS,成功直接设置占有线程然后返回
- 如果是自己占领的,就是直接重入
- 如果占有不成功,就会需要执行AQS的另外两个方法进入队列,然后等待获取锁
释放锁逻辑,非公平锁和公平锁释放都一样,
- 全部都是先计算state,
- 如果已经等于0了,就会设置占有线程为null
- 然后CASstate为我们的计算值
- 然后返回是否释放成功
**注意:**我开始在前面说的非公平锁两次CAS,公平锁一次CAS,其实严格意义上来说,除了非公平锁一开始的CAS外,其他的都是事先需要判断state是不是等于0的,不为0就不会进行一次CAS,所以说的两次是值有两次抢占锁的机会,不一定会进行CAS抢占