ReentrantLock中公平锁和非公平锁的区别
背景知识
ReentrantLock的组成
首先看下ReentrantLock的组成结构
ReentrantLock类图公平锁和非公平锁主要是通过内部类FairSync和NonFairSync中的tryAquire()方法来区分的
概述
ReentrantLock中的公平锁和非公平锁的主要区别为
- 公平锁中, 线程严格按照先进先出(FIFO)的顺序, 获取锁资源
- 非公平锁中, 拥有锁的线程在释放锁资源的时候, 当前尝试获取锁资源的线程可以和等待队列中的第一个线程竞争锁资源, 这就是ReentrantLcok中非公平锁的含义; 但是已经进入等待队列的线程, 依然是按照先进先出的顺序获取锁资源
公平锁示意图
公平锁示意图非公平锁示意图
非公平锁示意图注意: 以上图片中的Head是一个特殊的节点, 仅用于构造等待队列这个数据结构, 不包含等待的线程信息, 所以下一个可以获取锁资源的节点是Node1节点
源码解读
非公平锁
static final class NonfairSync extends Sync {
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
Sync中的nonfairTryAcquire()方法
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// flag 1 公平锁和非公平锁的主要不同点
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;
}
其中nonfairTryAcquire()方法是Sync类中实现的,注意flag 1处的代码, 只要state=0, 当前线程就有机会抢占该锁资源, 不考虑等待队列中是否已经有等待的线程
公平锁
static final class FairSync extends Sync {
final void lock() {
acquire(1);
}
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// flag 2 公平锁和非公平锁的主要区别
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;
}
}
flag 2处的代码显示, 竞争锁资源的前提条件是!hasQueuedpredecessord(), 即当前队列中没有其他线程在等待锁资源, 代码如下:
public final boolean hasQueuedPredecessors() {
//he correctness of this depends on head being initialized
//before tail and on head.next being accurate if thecurrent
//thread is first in queue.
Node t = tail; //Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
(
(s = h.next) == null ||
s.thread != Thread.currentThread()
);
}
简单解释一下这段代码,
- h==t意味着队列为空,返回false
- (h!=t) && (s=h.next)==null 意味着有一个线程尝试获取锁失败后,正在进行入队操作,而且 在AQS中国年enq()方法中, head=tail方法正好还没执行到, 此时队列被认为不空, 返回true, enq()代码如下:
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
// Head节点已经创建, 初始化操作完成了一半, tail=head尚未来执行,
// 到此已经能确定队列中存在等待锁资源的线程
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
- (h==t) && s.thread != Thread.currentThread() 此时队列不空, 但是第一个节点, 即上图中的Node1和当前线程是同一个线程, 意味着在当前线程之前没有其他等待的线程, 此时返回false
总之, hasQueuedPredecessors()方法只有返回false,当前线程才有资格尝试获取锁资源;假如返回true, 则意味着他必须排队等待
代码对比
公平锁
if (c == 0) {
// flag 2 公平锁和非公平锁的主要区别
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
非公平锁
if (c == 0) {
// flag 1 公平锁和非公平锁的主要区别
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
问题
阅读ReenTrantLock.FairSync.tryAcquire()方法,遇到两个有意思的问题
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
// flag 3 设置state使用了CAS
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
// flag 4 竞争的线程总数查过Int的最大值, 直接报错
throw new Error("Maximum lock count exceeded");
// flag 5 同样是设置state值, 此处却没有使用CAS
setState(nextc);
return true;
}
return false;
}
-
同样是state状态,flag 3 处使用了CAS, flag 5 处却没有使用CAS, 这是为啥呢? 因为当前线程为已持有锁的线程时(current == getExclusiveOwnerThread()), 只有他自己能修改state状态, 所以无需考虑并发修改的情况
-
flag 4 处竞争的线程总数超过int的最大值时, 会直接报错(抛出"Maximum lock count exceeded"异常), 然后在finally代码块中执行cancelAquire()操作, 将该线程对应的节点移出队列, 最后将异常抛给调用方, 调用方线程停止运行, 如下所示:
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
//flag 6
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
知识扩展
tryLock方法
这里同时补充一点, 不管Reentrant使用公平锁(FairLock)还是非公平锁(NonFairLock), ReentrantLock类的tryLock()方法都会调用Sync类的nonfairTryAcquire()方法,采取非公平的竞争策略
参考资料
公平锁与非公平锁
https://blog.csdn.net/m47838704/article/details/80013056
一些比较难看懂的方法
https://www.cnblogs.com/kumu/p/10659835.html