在了解AQS后,那应该怎么了解AQS的最佳实践那,我想再也没有Java官方的实践更加优秀了,这次我打算重新拿出系统源代码,并将其总结成一系列文章,以供将来查看.
本次准备分六篇文章用来分析基于AQS实现的类
本篇文章为系列文章的第一篇,本篇文章介绍ReentrantLock(可重入锁)非公平锁代码实现,ReentrantLock是一个可重入的互斥锁Lock,它具有与使用synchronized方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。
可重入锁指的是线程可以重复获取同一把锁.
首先,我们从总体过程入手,了解ReentrantLock非公平锁的执行逻辑,然后逐步深入分析源代码。
基于上面提到的过程,让我们来看看源代码实现逻辑.首先,让我们看看如果创建非公平锁。
//默认构造器 构建非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
//传值 构建 true 构建 公平锁,false 构建非公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
由上面代码可知,ReentrantLock提供了两个构造方法,默认无参构造器构建非公平锁
// 非公平锁 为一个内部类 ,他继承 Sync 内部类
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
@Override
final void lock() {
//当前线程直接尝试设置state (当前语义就是获取锁)
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
//如果失败执行 acquire (AQS提供独占模式)
acquire(1);
}
//重写AQS tryAcquire
@Override
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
由上面代码可知,NonfairSync继承SYNC,实现lock方法,通过lock方法可以知道,当加锁时,lock直接调用compareAndSetState设置当前线程,当执行失败的时候才执行AQS acquire方法. NonfairSync重写了tryAcquire,接下来上我们看下 tryAcquire的代码实现。
//抽象内部类 继承 AQS
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5965833819987602667L;
//声明抽象方法 lock
abstract void lock();
//ReentrantLock state 当前语义 0 未有线程获取锁 state > 0 代表已经存在线程占用
//执行不公平的tryLock。
//Try Acquire是在子类中实现的,但两者都需要对trylock方法进行不公平的尝试。
final boolean nonfairTryAcquire(int acquires) {
//获取当前线程
final Thread current = Thread.currentThread();
//获取当前状态
int c = getState();
//如果未有线程占用
if (c == 0) {
//尝试直接设置 state 状态 (这里是第二次尝试不参与AQS排队)
if (compareAndSetState(0,acquires)) {
//如果设置成功 设置setExclusiveOwnerThread 为当前线程
setExclusiveOwnerThread(current);
return true;
}
}
//这个判断就是可重入锁实现的支持
//如果当前线程等于 getExclusiveOwnerThread ,标识线程已经获取锁
else if (current == getExclusiveOwnerThread()) {
//当前线程累加 acquires
int nextc = c + acquires;
//如果 nextc < 0 标识非法 state 状态
if (nextc < 0) {
throw new Error("Maximum lock count exceeded");
}
//设置state 由于是同一个线程操作(线程重新进入),不存在安全问题,
//所以可以直接使用setState设置state
setState(nextc);
return true;
}
//如果以上都没有设置成功
//则返回失败 执行AQS排队机制
return false;
}
//后续代码省略
}
以上入口操作要说明三点 第一,lock获取锁失败后,通过AQS acquire 方法执行调用nonfairTryAcquire 再次尝试获取锁, 如果获取状态为0 则nonfairTryAcquire 会再次尝试直接设置(获取锁)setStae值,如果获取成功则不再执行AQS排队操作,如果获取失败则执行AQS排队.
第二,nonfairTryAcquire方法是支持可重入操作的,如果当前线程获取锁失败,会在判断当前线程是否等于获取到锁的线程,如果等于则当前线程在状态增加(+acquires),不在参与AQS排队
第三,我们通过观察可重入锁发现,每次可重入时当前状态递增,state 是int类型,所以由此推算可重入锁的递归深度是2147483648 也就是int值(2^31-1)范围
接下来,看看释放锁的代码:
//
abstract static class Sync extends AbstractQueuedSynchronizer {
//省略部分代码
//实现tryRelease
@Override
protected final boolean tryRelease(int releases) {
//当前状态减去releases(因为存在递归)
int c = getState() - releases;
//如果当前线程不等于获取锁的线程 则抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//如果状态等于0 则表示当前线程释放了锁资源
//针对锁资源唤醒的优化 只有当前线程完全释放锁了才自行唤醒操作
//频繁唤醒,会导致锁竞争增加,cpu资源浪费
if (c == 0) {
//free 设置为true,AQS代码研究是可以知道 tryRelease返回true
//AQS会唤醒内部阻塞节点 竞争锁资源
free = true;
setExclusiveOwnerThread(null);
}
//设置状态
setState(c);
return free;
}
//省略部分代码
}
释放锁代码比较简单,但是我们依然看见了锁资源的优化,就是只有当前线程完全释放锁资源后,才会让AQS唤醒后续节点,这样避免频繁唤醒线程,减少锁竞争
以上是ReentrantLock的获取和释放源码分析,它的实现其实很简单,这些都因为它基于AQS实现,AQS已经帮我们实现了大多数功能,了解ReentrantLock源码实现能够让我们更加深入的了解AQS设计思想。