ReentrantLock
这篇文章是从JDK8的ReentrantLock源码角度来分析ReentrantLock是怎么利用AQS来实现公平锁,和非公平锁的。
所以前提需要理解AQS。
简单的翻译了一下,大概清楚JDK doc里ReentrantLock介绍
/**
* A reentrant mutual exclusion {@link Lock} with the same basic
* behavior and semantics as the implicit monitor lock accessed using
* {@code synchronized} methods and statements, but with extended
* capabilities.
ReentrantLock属于非共享锁,即独占锁,每次只能是一个线程取得锁资源,它与synchronized关键字具有相同的锁语义,但是ReentrantLock具有一些另外的拓展功能
*
* A {@code ReentrantLock} is owned by the thread last
* successfully locking, but not yet unlocking it. A thread invoking
* {@code lock} will return, successfully acquiring the lock, when
* the lock is not owned by another thread. The method will return
* immediately if the current thread already owns the lock. This can
* be checked using methods {@link #isHeldByCurrentThread}, and {@link
* #getHoldCount}.
ReentrantLock锁只属于获得它的线程,直到unlock解锁。如果一个线程执行lock(),如果当前锁没有
其他线程在占用,则会获取得锁,立即返回。
如果该线程已经拥有当前锁,再执行lock(),也会再次获得锁,立即返回。
当前线程是否持有当前锁,可以用锁的isHeldByCurrentThread()来查看获得当前锁的线程。
*
* <p>The constructor for this class accepts an optional
* <em>fairness</em> parameter. When set {@code true}, under
* contention, locks favor granting access to the longest-waiting
* thread. Otherwise this lock does not guarantee any particular
* access order.
ReentrantLock的构造函数可以传入一个boolean类型的true,来表示构造一个公平的锁,此时锁倾向于最长
等待时间的线程。
如果默认。或者传入函数为false。则为非公平锁,此时不保证获取得到锁的线程顺序。
Programs using fair locks accessed by many threads
* may display lower overall throughput (i.e., are slower; often much
* slower) than those using the default setting, but have smaller
* variances in times to obtain locks and guarantee lack of
* starvation.
使用公平锁的吞吐率其实在某些情况会比使用非公平锁低,但是可以解决饥饿现象
Note however, that fairness of locks does not guarantee
* fairness of thread scheduling. Thus, one of many threads using a
* fair lock may obtain it multiple times in succession while other
* active threads are not progressing and not currently holding the
* lock.
* Also note that the untimed {@link #tryLock()} method does not
* honor the fairness setting. It will succeed if the lock
* is available even if other threads are waiting.
值得注意的是,tryLock()属于非阻塞方法,成功则返回true,失败则返回false,
这个方法无所谓的公平和非公平之分,即使是设置的是公平锁,任何线程调用这个方法,都会尝试获得锁资源,
即使等待队列中有其他线程正在等待该锁资源
*
* <p>It is recommended practice to <em>always</em> immediately
* follow a call to {@code lock} with a {@code try} block, most
* typically in a before/after construction such as:
*
* <pre> {@code
* class X {
* private final ReentrantLock lock = new ReentrantLock();
* // ...
*
* public void m() {
* lock.lock(); // block until condition holds
* try {
* // ... method body
* } finally {
* lock.unlock()
* }
* }
* }}</pre>
*
* <p>In addition to implementing the {@link Lock} interface, this
* class defines a number of {@code public} and {@code protected}
* methods for inspecting the state of the lock. Some of these
* methods are only useful for instrumentation and monitoring.
*
* <p>Serialization of this class behaves in the same way as built-in
* locks: a deserialized lock is in the unlocked state, regardless of
* its state when serialized.
*
* <p>This lock supports a maximum of 2147483647 recursive locks by
* the same thread. Attempts to exceed this limit result in
* {@link Error} throws from locking methods.
同一线程最大可重入锁的次数是 2的31次方减1,就是2147483647
*
* @since 1.5
* @author Doug Lea
*/
IDEA里面的UML图显示,ReentranLock里面组合了Sync内部类,而Sync是继承了AQS。
其次,NonfairSync,fairSync是集成自Sync的两个内部类,分别对应非公平与公平的AQS。
再仔细看,NonfairSync,fairSync只是重写了Sync的lock()方法和重写了AQS的tryAcquire()
首先是,构造函数
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
再来看看内部继承AQS的同步器Sync,Sync作为ReentrantLock的内部类。
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
/**
* Performs {@link Lock#lock}. The main reason for subclassing
* is to allow fast path for nonfair version.
*/
abstract void lock(); // NonfairSync,fairSync 要继承此Sync要实现的
/**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
注意这里,因为Lock接口的语义里,tryLock()是不分公平与非公平的,(实际语义其实等同于非公平的)
所以初始构造ReentrantLock时候,无论是new的fairSync还是notfairSync,都应该可以执行tryLock(),
因此tryLock()用到的Sync(AQS)的nonfairTryAcquire(),应该在NonfairSync,fairSync父类中实现,就是此类Sync,这样才可以达到共用的效果。
*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) { //CAS安全地更新state
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); //此时是重入,所以这里可以不用CAS来改变state
return true;
}
return false;
}
//此tryRelease为NonfairSync,fairSync共用,因为释放无所谓公平不公平
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) { //state==0 才解除线程持有锁
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
protected final boolean isHeldExclusively() {
// While we must in general read state before owner,
// we don't need to do so to check if current thread is owner
return getExclusiveOwnerThread() == Thread.currentThread();
}
final ConditionObject newCondition() {
return new ConditionObject();
}
// Methods relayed from outer class
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}
final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
}
final boolean isLocked() {
return getState() != 0;
}
/**
* Reconstitutes the instance from a stream (that is, deserializes it).
*/
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
setState(0); // reset to unlocked state
}
}
再来看看 NonfairSync ,也就是默认的非公平模式
/**
* Sync object for non-fair locks
*/
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
@Override //重写父类Sync的抽象方法Lock(),其实公平和非公平,区别在这,非公平lock()的时候,都会先尝试获取锁资源,失败了,再进入acquire()流程。
//而公平锁却直接进入acquire()流程,需要检验等待队列中是否有其他线程在等待,若无,才尝试获取锁
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
//直接调用的是Sync定义的nonfairTryAcquire()
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
再来看FairSync
/**
* Sync object for fair locks
*/
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
//这里不同于非公平,这里直接进入acquire()
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) {
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;
}
}
所以说,非公平锁,lock的时候,会无视正在等待锁资源的队列里面是否有成员,而直接尝试一次获取,若不成功,则还是会进入AQS的CLH等待队列,然后阻塞,顺序等待唤醒,获取。
公平锁,lock的时候,则不能无视正在等待锁资源的队列里面的成员。
最后,看下ReentrantLock的提供给我们使用的接口函数,是怎样调用fairSync,nonfairSync的
public void lock() {
sync.lock();
}
分派给NonfairSync的lock()
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
分派给fairSync的lock()
final void lock() {
acquire(1);
}
而tryLock(),直接是用的sync内部类定义的nonfairTryAcquire(),这也是为什么nonfairTryAcquire()定义在Sync,而不是NonfairSync()的原因。
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
总结,Doug Lea,非常巧妙的利用AQS的模板方法,简洁而清晰地实现了具有公平和非公平的模式的ReentrantLock,值得我们去学习。