重入锁ReentrantLock有两种方式获取锁,一种是公平性,一种是非公平性。如果在绝对时间上,先请求获取锁的线程一定会先获取到锁,那么这个锁就是公平的,反之,这个锁就是不公平的。公平的获取锁,也就是等待时间最长的线程最优先获取锁,也可以说锁获取是顺序的。那重入锁ReentrantLock如何实现公平锁,以及非公平锁,它们的区别又是什么?
重入锁ReentrantLock默认使用非公平获取锁,同时提供构造函数,能够控制锁是否是公平。
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
公平锁使用FairSync同步组件,非公平锁使用NonfairSync同步组件。FairSync与NonfairSync均是ReentrantLock的内部类,又共同继承了ReentrantLock的一个内部类Sync。FairSync与NonfairSync不同的地方在于重写Sync的lock()方法与tryAcquire()方法。我们先看一下重入锁ReentrantLock默认的非公平锁如何获取锁。
非公平性获取锁调用NonfairSync的lock()方法
final void lock() {
// 如果当前同步状态为O,表示锁未被任何线程获取,CAS设置同步状态为1
if (compareAndSetState(0, 1))
// 设置当前线程为获取锁的线程
setExclusiveOwnerThread(Thread.currentThread());
else
// 如果锁已经被某线程获取,尝试获取锁
acquire(1);
}
NonfairSync的lock()方法会调用同步器acquire方法,最终会调用NonfairSync的nonfairTryAcquire方法来获取同步状态。
final boolean nonfairTryAcquire(int acquires) {
// 获取当前线程
final Thread current = Thread.currentThread();
// 获取同步状态
int c = getState();
// 同步状态为0,表示没有线程获取锁
if (c == 0) {
// CAS获取同步状态
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会判断同步状态是否为0,如果同步状态是0,说明目前没有任何线程获取到锁,设置获取锁的线程为当前线程并返回true,表示同步状态成功。当同步状态不为0时,判断当前线程是否为获取锁的线程,如果是获取锁的线程再次请求,则将同步状态的值进行增加并返回true,表示同步状态成功。
在非公平性获取锁中,只要CAS设置同步状态成功,则表示当前线程获取了锁。那公平性是如何获取锁?
公平性获取锁调用FairSync的lock()方法
final void lock() {
// 获取锁
acquire(1);
}
FairSync的lock()方法调用同步器acquire方法,最终会调用FairSync的tryAcquire方法来获取同步状态。
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 同步状态为0,表示没有线程获取锁
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;
}
}
我们把该方法与非公平获取锁时调用的nonfairTryAcquire相比较,唯一不同的位置在于当同步状态为0时,增加hasQueuedPredecessors判断,而该方法判断在同步队列中当前节点是否有前驱节点,如果该方法返回true,表示有线程比当前线程更早的请求获取锁,因此需要等待前驱线程获取并释放锁后才能继续获取锁。
注:hasQueuedPredecessors方法为同步器AbstractQueuedSynchronizer中的方法,锁的语义均由同步器AbstractQueuedSynchronizer实现。
public final boolean hasQueuedPredecessors() {
// 同步队列尾节点
Node t = tail;
// 同步队列头节点
Node h = head;
Node s;
// 判断头节点的后继节点的线程是否为当前线程,即判断当前节点是否有前驱节点
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
重入锁ReentrantLock在获取锁时通过调用hasQueuedPredecessors方法来实现公平锁的语义。