前言
上一篇文章 ReentrantLock 源码分析 - 非公平锁 lock()方法 分析了 非公平锁 的实现方式。
本文接着介绍 公平锁 的具体实现,什么是公平锁和非公平锁请参考
正文
知识回顾
AQS:
AQS 是一个双向队列,从队列中的第二个节点开始,节点中的线程才可以参与锁竞争,第一个节点被称为“虚节点” 或者 “哨兵节点”。
非公平性具体体现:
一个线程 A 调用 lock()
方法去抢占锁时, 它并不会考虑 AQS 队列 中是否有其他线程在等待,它会认为我们处于同一起跑线,谁抢到算谁的,但是明显 AQS 队列中的线程来的早,先到先得嘛, 如果刚来的线程 A 先手抢到锁,显然是不公平的。
图解队列的状态
1. AQS 队列初始状态
2. 当第一个线程节点进入队列
这个过程分为两个步骤:
2.1 初始化一个哨兵节点
new Node()
, Node 中的 thread
变量为 null。
2.2 节点入队
3. thread1 获得锁之后
公平锁的 lock() 实现
公平锁的 lock 实现在 FairSync
类中,可以看出是调用的 AQS 的 acquire(1)
方法。
final void lock() {
acquire(1);
}
acquire(int arg)
方法前面提到了很多次, 首先是调用 tryAcquire(arg)
方法, 而tryAcquire(arg)
这个抽象方法由继承自 AQS 的 FairSync
实现的
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
FairSync
的tryAcquire
实现方式与上一篇文章 ReentrantLock 源码分析 - 非公平锁 lock()方法 的实现方式大致相同, 只不过加了hasQueuedPredecessors()
这个公平策略,我们来后面会着重讲下这个方法。
先从与语义逻辑上看下公平锁的 tryAcquire
实现
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 如果锁未被其他线程占用
if (c == 0) {
// 如果 AQS 队列中, 当前线程前面没有其他线程比他排队更久(公平策略)
// 并且 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;
}.
hasQueuedPredecessors() 公平策略实现
public final boolean hasQueuedPredecessors() {
// 头节点=head=h, 尾节点=tail=t
Node t = tail;
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
从语义上理解:
就是当前线程的前驱节点 不是 队列的头节点,就返回 true, 表示有的线程比当前线程等的要久。
分步骤拆解:
-
如果
h == t(即 head == tail
) 队列为空,表示没有其他线程等待,直接返回 false
-
如果
h != t && h.next == null
, 表示第一个节点 即将入队,也就是头节点的后继指针还未指向,thread1
节点(只能说一只脚迈进了队列
),表示thread1
比当前线程来的早,此时返回 true,
-
如果
h.next != null
, 那么此时队列的状态如下图所示:
那么此时只能通过 s.thread != Thread.currentThread()
,判断 thread1 是否等于当前的线程,如果不是,表示 thread1
比当前线程 来的早,返回 true,否则返回 false。
总结
ReentrantLock
的非公平锁 和 公平锁的主要区别在于 tryAcquire(int arg)
方法实现上。
公平锁在该方法的实现上比非公平锁多加了一个 公平策略,也就是 hasQueuedPredecessors()
方法。
再次强调两点:
- 请先熟悉前面的系列文章内容, 再来阅读本文, 前后的文章是相关联的。
- 先从代码语义去掌握整个
lock
方法的逻辑流程, 再去看代码的实现,看不懂的可以先略过。