背景
- 最近无聊翻一翻 juc ,想看 AQS 入队的是怎么入队的,由于代码量比较大,就想先看了 ReentrantLock 入队操作,然后举一反三,由于我看的是 JDK 17 的版本,与 JDK 8 可能不一样,先不去考证 JDK 8 内容,只看 JDK 17 的代码,并且只讨论 ReentrantLock 中的方法
读代码
lock()
- 首先肯定要读 lock() 和 unLock() 两个方法,实际写代码就是用这两个完成重入锁操作
public void lock() {
sync.lock();
}
肯定走这个方法了吧,当我 ctrl + 鼠标左键调到具体方法时,发现并没有这样显示:
而是直接跳到了
@ReservedStackAccess
final void lock() {
if (!initialTryLock())
acquire(1);
}
这个 initialTryLock() 是一个抽象方法,而 acquire(1) 调用的是 AQS 类的方法,AQS 的类代码先不讨论,反正不管公平还是非公平锁最后都调用 acquire(),也就是入队 的简称 ;
先看 ReentrantLock 的内容,当我点击 initalTryLock() 方法时出现了,公平锁和非公平锁两个选项:
现在比较一下两个不同的 initialTryLock() 实现:
在 idea 中打开两个相同的类
两部分代码其实只有红色的部分不一样:
相比较之下只是公平锁多出了:
int c = getState();
if (c == 0) {
if (!hasQueuedThreads()
很好理解,先获取 AQS 中 state 变量的属性,如果属性是 0,再去看线程队列中有没有人排队,没人排队再去竞争下,很显然公平锁还是讲武德的,别人在排队了,我就不去竞争了;相比较,非公平锁,来了就去竞争下,没竞争到再入队,这年轻人xxxx;
这里会有疑问,什么是 state 属性,什么是队列呢,说说我自己的理解?
-
要从 AQS 设计说起,AQS 本身就是一个普通抽象类,与我们平时自己写的抽象类一样,里面有一个用 volatile 关键字修饰 int 类型的成员变量 state ,因为有 volatile 关键字修饰,那么多个线程都能看见这个 state ,如果多个线程竞争同一个资源时,都去参考 state 的值,这个 AQS 不就相对于一把锁了吗,当 state=0 时,代表没人竞争,代码继续执行,当 state > 0 时,代表有人持有这个资源,就去等待,等持有锁的线程执行完再去竞争资源,执行这个锁与 synchronized 相比哪个性能好先不去讨论,先看 lock 时引出的两个问题;
-
一个是 state 是值从 0 变为大于 0 ,怎么保证原子性,另一个是当 state > 0,等待的线程去哪里等待或者叫记录他们线程信息 和 先来后到顺序;第一个问题有 cas 算法呀,第二个使用 node 队列解决, node 有一个属性 waiter 记录线程信息,队列来保证先来后到顺序;这不就行了吗
-
当 state >1 代表什么呢,在 ReentrantLock 中代表重入次数,在 CountDownLatch 中代表倒计次数开门闩,其他的 juc 类没怎么看,就不说了;
unLock()
unLock() 代码 ReentrantLock 的公平锁和非公平锁调用的是同一套释放锁方法:
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
signalNext(head);
return true;
}
return false;
}
@ReservedStackAccess
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;
}
这里就不展开说了,写的有点长了, tryRelease(arg) 就是 tryRelease(1),tryRelease(1) 就是把 AQS 中的 state 属性减 1,当减到 0 时,返回 ture,通知 AQS 队列头结点的下一个;
整体架构评价
- 在看 JDK17 的 ReentrantLock 时我发现代码结构更精简了。以前我以为有两个内部类 FairSync 和 NonFair ,现在有多了一个 Sync 类,把 FairSync 和 NoFair 的共有属性放到了 Sync 类,首先FairSync 和 NonFairSync 都变精简了,其次方便观察实现不同: