上一篇中,我们在源码的角度讲了AbstractQueuedSynchronizer的原理,链接为 Java 并发编程之AbstractQueuedSynchronizer源码解析 ,这一篇中,我们从源码的角度讲一下 ReentrantLock 的原理。
我们知道,ReentrantLock是我们在jdk中经常使用的可重入锁的实现类,内部分为公平的可重入锁以及不公平的可重入锁,那么这些机制是如何实现的呢?
一、ReentrantLock如何实现的可重入锁?
可重入锁,顾名思义,即当我这个线程拿到锁之后,即调用lock()方法,继续调用lock()方法也可以再次获取到锁。当然调用多少次lock()就需要调用多少次unLock()来释放锁。
我们看一下ReentrantLock中公平锁与不公平锁的抽象父类:
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
. . .
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 如果为0,说明是第一次获取锁
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");
// 如果是,那么直接设置状态为 c + acquires
setState(nextc);
return true;
}
return false;
}
. . .
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
// 当为拿到锁的线程时,直接置为相应state
setState(c);
return free;
}
}
可以看到,nonfairTryAcquire()方法来获取锁的时候,增加了是否是当前线程的判断,就是说ReentrantLock中增加了拿到锁的线程标示位。当这个线程再次获取锁的时候,直接增加state即可。
二、ReentrantLock的公平锁与不公平锁的实现:
ReentrantLock如何实现的公平锁与不公平锁?
1、不公平锁的实现:
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);
}
}
可以看到,lock()方法直接先试着获取了锁,如果获取不到,直接调用 Sync 的 nonfairTryAcquire()方法来获取锁。这样一个线程A获取到锁并释放之后,即使现在有很多线程在等待,此线程A仍有可能再次获取到锁。
2、公平锁的实现:
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;
}
}
可以看到,FairSync直接实现了 tryAcquire() 这个方法,比较重要的就是 !hasQueuedPredecessors() 这个判断,即当c为0的时候,会有 !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());
}
即判断是否有线程等待时间比当前线程等待时间更长,那么我们看上述例子,当线程A获取到锁之后,此时线程B、线程C排队等待获取锁。那么当线程A释放锁之后,线程A再次获取到锁,hasQueuedPredecessors()会返回true,因为此时此时有线程在排队,且等待时间更长,此时只有线程B调用hasQueuedPredecessors()方法才返回false,也就是说,只有线程B才能获取到锁。这样公平锁即实现了。