聊聊ReentrantLock基于AQS的公平锁和非公平锁的实现区别

2022-11-18,,,,

ReentrantLock锁的实现是基于AQS实现的,所以先简单说下AQS:

AQS是AbstractQueuedSynchronizer缩写,顾名思义:抽象的队列同步器,它是JUC里面许多同步工具类实现的核心

其实简单来说AQS有两个核心,一个是volatile修饰的int类型state,这个是记录处于等待中需要持有锁和正在持有锁的线程数量

/**
* The synchronization state.
*/
private volatile int state;

第二个就是Node内部类,他是AQS里面FIFO双向队列节点的实现,他的一些属性如下:

static final class Node {
     volatile int waitStatus;//等待状态 volatile Node prev;//前驱节点 volatile Node next;//后继节点 volatile Thread thread;//申请同步状态的线程 Node nextWaiter;//等待节点的后继节点(后续出AQS详细篇再细讲)
}

这种结构可以从任意的一个节点开始很方便的访问前驱和后继节点。每个Node由线程封装,当线程竞争失败后会加入到AQS队列中去。

基于这些再继续聊下ReentrantLock的公平和非公平锁的实现

ReentrantLock锁的实现基于AQS,如下sync抽象内部类:

abstract static class Sync extends AbstractQueuedSynchronizer{
abstract void lock();
}

Sync的子类有两个:FairSync和NonfairSync(公平和非公平锁的具体实现类),两个锁的不同就在于两个方法的实现不同lock():加锁的方法,还有tryAcquire(int acquires):尝试持有锁的方法,获取成功返回true,失败返回false

看看两把锁的源码实现:

    static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
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;
}
}

  

static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
} nonfairTryAcquire方法是在父类Sync中定义: 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;
}

可以看见,在使用lock()加锁的时候就已经体现了非公平性了,因为lock()加锁的时候直接尝试使用CAS获取锁,如果获取到了就不会入等待队列,所以会有后来的线程先抢占到锁;

那如果没有抢占到锁呢?会走else的acquire(1)方法。

先看看acquire方法的定义:

public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}

这里会发现acquire调用了tryAcquire方法,而在上面两把锁的源码中各自重写了tryAcquire方法

这个方法中当getState()获取同步状态为0(没有线程持有锁)时候,继续使用用CAS获取锁

两个重写的方法唯一不同之处在于,公平锁加了hasQueuedPredecessors()方法:

public final boolean hasQueuedPredecessors() {
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());
}

这个方法主要是用来判断线程需不需要排队,因为队列是FIFO的,所以需要判断队列中有没有相关线程的节点已经在排队了。有则返回true表示线程需要排队,没有则返回false则表示线程无需排队。

而非公平锁就没有判断FIFO队列是否还有排队。

小结:

ReentrantLock中公平锁和非公平锁的实现主要有两处区别

1.在lock()方法中,非公平锁一进来就尝试CAS获取锁,不会管等待队列里面是否有等待线程

2.在tryAcquire方法中,判断state等于0(没有线程持有锁的情况)后,公平锁会先调用hasQueuedPredecessors()方法判断FIFO队列是否有等待线程,没有才继续尝试获取锁,而非公平锁是直接CAS获取锁

聊聊ReentrantLock基于AQS的公平锁和非公平锁的实现区别的相关教程结束。

《聊聊ReentrantLock基于AQS的公平锁和非公平锁的实现区别.doc》

下载本文的Word格式文档,以方便收藏与打印。