前言
前面用了 4 篇文章为本文分析 ReentrantLock
源码做了一些铺垫,
ReentrantLock 源码分析 - AbstractQueuedSynchronizer 详解(一)
ReentrantLock 源码分析 - AbstractQueuedSynchronizer 详解(二)
赶紧趁热打铁,来看下 ReentrantLock
非公平锁的lock()
方法是如何实现的。
正文
知识回顾
ReentrantLock
既可以实现非公平锁,也可以实现公平锁。
通过 ReentrantLock lock = ReentrantLock(false)
的方式可以创建一个非公平锁。
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
从上面的构造方法可以看出, 当传入 false
时,new 了个 NonfairSync
对象。
NonfairSync 类
通过查看源码,可以看出NonfairSync
继承 Sync
。
那 Sync
又是什么? 请往后看。
static final class NonfairSync extends Sync {
}
Sync 类
Sync
继承自我们熟悉的 AQS
, 前面花了很多篇幅去介绍 本文会涉及 到的 AQS
部分实现。Sync
中有一个nonfairTryAcquire(int acquire)
方法,该方法在前面的文章中也举例说明过,
我们先跳过这段源码分析,接着往下看 :
abstract static class Sync extends AbstractQueuedSynchronizer {
// 知识回顾:前面有介绍 ReentrantLock 是可重入锁,state 表示重入次数
final boolean nonfairTryAcquire(int acquires) {
// 获取当前线程
final Thread current = Thread.currentThread();
// 获取 state
int c = getState();
// 如果 state == 0,表示锁未被占用,
// 然后通过 CAS 操作,把 state + 1,标记锁被占用
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 如果锁被线程 A 占用
// 判断当前线程是否就是线程 A
// 如果是,就把 state +1
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
lock()
回归初心,来看下 lock 的调用方式
ReentrantLock lock = new ReentrantLock(false);
lock.lock();
而 lock() 的实现如下:
public void lock() {
sync.lock();
}
根据前面讲的,此时 sync = NonfairSync()
,
而 NonfairSync
的 lock
方法实现如下:
final void lock() {
// 通过 CAS 原子操作,把 state 由 0 设置为 1
if (compareAndSetState(0, 1))
// 设置成功,把当前线程标记为独占锁持有者
setExclusiveOwnerThread(Thread.currentThread());
else
// 如果 cas 失败,表示抢占锁失败,把当前线程放入 AQS 队列,入队后,还会有一系列操作
// AQS 的 acquire(int arg) 方法, 上文有详细讲过
acquire(1);
}
tryAcquire(int arg) 方法
前面提到了 AQS 的 acquire
方法中调用了 tryAcquire
方法,该方法主要目的是获取锁资源, 它本身在 AQS 中是抽象方法,由具体的子类实现的(在此处就是 NonfairSync
), 而 tryAcquire
方法又调用了nonfairTryAcquire
方法, 这时候我们就可以 传送
到 Sync 类 介绍部分,去阅读那段nonfairTryAcquire
方法的源码
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
ReentrantLock 的非公平性
上面只是讲了非公平锁 lock() 方法的执行流程,来看下它的非公平性体现在哪
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
// 步骤一
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
当线程 1 执行到此方法时,获取到 state !=0 ,表示 锁已经被线程 A 占用了,
然后判断线程1是否等于 线程 A, 如果不是返回 false,然后进入队列。
在线程 1 入队的同时,线程 2 也来到此方法,然后获取到 state == 0,通过 CAS 操作拿到锁。
明明是 线程 1 先来的,但是锁却被后来的线程 2 拿到了,这明显是不公平的。
梳理一下
-
所有的线程都是根据
state
的值来判断,锁是否被占用,state == 0
,表示未被占用 -
state
的值是一直在变化的,state
被 N 多个线程在读写 -
N 多个线程来自于 两个方向 的线程,其中一波是 之前抢占锁失败被放入 AQS 队列的线程,另一波是 “新来的” (比如刚刚
start()
的一些线程)。 -
明显前面一波线程来得更早,但是两个方向的线程却不区分先后顺序,因此“新来的”线程很可能会比,AQS 队列的线程先抢到锁。
-
以上就是不公平的体现。
总结
可以看出本文内容非常少,我们前期花了大量的功夫去挖掘一些基础知识,因为 ReentrantLock
本身就是基于 AQS 实现的,而 AQS 又依赖于下面的小知识
因此把前面的基础打好,ReentrantLock
的具体实现就很容易看懂了。
再次总结下本文主要内容:
- ReentrantLock 可以通过
new ReentrantLock(false)
方式实现非公平锁 - 第 1 步 产生的结果就是 new 了一个
NonfairSync
- 而
NonfairSync
本文继承自Sync
, 而Sync
又继承自 AQS - 当调用非公平锁的
lock()
方法时,就是调用NonfairSync
的lock
方法 - 第 4 步就是一个抢占锁的过程
- 抢占成功与否是根据 CAS 操作结果判定的
- 抢占失败的线程会调用
acquire
方法,继续尝试获取锁,获取失败就会被被放入AQS
队列(具体策略看上一篇文章)