在上篇文章中我们结合源码介绍了AQS
的数据结构及实现原理,在今天的文章中我们介绍下ReentrantLock
的实现原理,也看看在ReentrantLock
中是如何使用AQS
的。
本篇文章的内容,依赖于上篇文章,大家需要先了解下AQS
的源码,文章连接为:AQS源码
ReentrantLock
是一个Java层面的锁的实现,也是我们常用的一个锁对象,是一个可重入的排他锁。
1 类结构
ReentrantLock
是Lock
的一个实现类,实现了Lock
中定义的锁应该具备的功能。其类图如下:
通过源码我们看到,在ReentrantLock
中只有一个Sync
类型的变量sync
,锁功能便是通过改类及其子类实现的,Sync
的类图如下:
通过类图我们发现了上篇文章中介绍的AQS
的身影,该类继承自AQS
,并有两个实现类FairSync
、NonfairSync
,这两个类分别是ReentrantLock
的公平和非公平实现。
2 lock和unlock
锁最主要的作用就是加锁和释放锁。在这部分我们会根据源码了解下其实现原理。
ReentrantLock.Sync
中的方法如下:
在上篇文章中我们知道AQS
中有些方法是需要子类进行实现的,在上图中我们可以看到其实现了tryAcquire
和tryRelease
两个方法。
接下来我们分别看下ReentrantLock
中的公平锁和非公平锁都是如何处理的。
2.1 获取非公平锁
NonfairSync
中的lock
方法源码如下:
final void lock() {
// 通过CAS操作修改state值
if (compareAndSetState(0, 1))
// 抢占锁成功 设置占用独占锁的线程为当前线程
setExclusiveOwnerThread(Thread.currentThread());
else
// 抢占锁失败调用该方法 AQS中的方法 在该方法中会调用tryAcquire方法
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
// 获取当前线程
final Thread current = Thread.currentThread();
// 获取state值
int c = getState();
// state是0代表为无锁状态
if (c == 0) {
// CAS操作替换state
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");
// 修改state值 这里无需使用CAS操作
setState(nextc);
return true;
}
return false;
}
ReentrantLock
的非公平锁的获取逻辑还是比较简单的,调用lock
方法时先进行抢占锁操作,抢占失败了再加入阻塞队列。
2.2 获取公平锁
FairSync
中的lock
方法源码如下:
final void lock() {
// 直接调用AQS中的acquire方法
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
// 当前线程
final Thread current = Thread.currentThread();
// 当前状态值
int c = getState();
// state为0代表无锁状态
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;
}
公平锁的加锁逻辑也是比较简单的,当阻塞队列中没有其他等待线程时才进行抢占锁操作,这便是和非公平锁的不通之处。
2.3 锁的释放
ReentrantLock.Sync
中的tryRelease
方法源码如下:
protected final boolean tryRelease(int releases) {
// state值减去需要释放的锁的数量
int c = getState() - releases;
// 当前线程非获得独占锁的线程 抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
// 是否全部释放
boolean free = false;
if (c == 0) {
free = true;
// 设置独占锁的占有线程为null
setExclusiveOwnerThread(null);
}
// 修改state值
setState(c);
// 是否释放掉 释放成功会走AQS中唤醒阻塞队列下个节点的逻辑
return free;
}
至此ReentrantLock
中锁的获取和释放逻辑,我们便讲解完了,逻辑还是比较简单的,建议大家在看源码时再点到AQS
中看看,加深下对AQS
的理解。
ReentrantLock
的锁状态是通过state
属性进行标记的,其取值如下:
- 0 无锁
- 大于0 线程获取锁的次数 重入时累加其值
获取锁和释放锁时对state进行加减操作,减到0时代表释放锁成功,唤醒AQS
阻塞队列中的下个节点进行锁竞争。
公平锁和非公平锁的区别时,阻塞队列中存在其他阻塞线程,公平锁就插入阻塞队列尾部,非公平锁是在进行添加操作时都会进行一次锁竞争。
3 总结
在今天的文章中介绍了,ReentrantLock
是如何在AQS
的基础上实现的锁的功能,没什么复杂的逻辑。大家可以结合着AQS
的源码一起来看看。
今天的文章就到这里了,在下次的文章中会介绍CountDownLatch
同步工具的原理,大家可以先看看,其也是通过AQS
实现的,逻辑也不难。
如果感觉对您有帮助,欢迎关注下公众号,您的关注是我更新的最大动力~