前言
重入锁,即ReentrantLock
,继承于Lock
接口,提供锁重入功能。重入锁与不可重入锁的区别在于,重入锁支持已经获取锁的线程重复对锁资源进行获取。
正文
Java
中的synchronized
关键字可以隐式的支持锁重入功能,考虑如下一个例子。
public class HelloUtil {
public static synchronized void sayHello() {
System.out.print("Hello ");
sayWorld();
}
public static synchronized void sayWorld() {
System.out.println("World");
}
}
已知访问由synchronized
关键字修饰的静态方法时需要先获取方法所在类的Class
对象作为锁资源,所以当A线程调用HelloUtil
的sayHello()
方法时,需要获取的锁资源为HelloUtil
类的Class
对象,此时B线程再调用HelloUtil
的sayHello()
或sayWorld()
方法时会被阻塞,但是A线程却可以在sayHello()
方法中再调用sayWorld()
方法,即A线程在已经获取了锁资源的情况下又获取了一次锁资源,这就是synchronized
关键字对锁重入的支持。
结合上面的例子,已经对重入锁有了直观的认识,下面将分析ReentrantLock
是如何实现重入锁的。ReentrantLock
的类图如下所示。
ReentrantLock
有三个静态内部类,其中Sync
继承于AbstractQueuedSynchronizer
,然后FairSync
和NonfairSync
继承于Sync
,因此Sync
,FairSync
和NonfairSync
均是ReentrantLock
组件中的自定义同步器,且FairSync
提供公平获取锁机制,NonfairSync
提供非公平获取锁机制。公平和非公平获取锁机制现在暂且不谈,下面先看一下Sync
,FairSync
和NonfairSync
实现了哪些方法,如下所示。
由上述可知,ReentrantLock
默认使用非公平获取锁机制,然后可以在构造函数中根据传入的fair参数决定使用哪种机制。现在先对上面的讨论做一个小节:ReentrantLock
是可重入锁,即已经获取锁资源的线程可以重复对锁资源进行获取,ReentrantLock
内部有三个自定义同步器,分别为Sync
,NonfairSync
和FairSync
,其中NonfairSync
和FairSync
能分别提供非公平获取锁机制和公平获取锁机制,具体使用哪一种获取锁机制,需要在ReentrantLock
的构造函数中指定。
接下来结合NonfairSync
和FairSync
的lock()
和tryAcquire()
方法的源码,对非公平获取锁机制和公平获取锁机制进行说明。
NonfairSync
的lock()
方法如下所示。
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
非公平获取锁调用lock()
方法时会先将state以CAS方式从0设置为1,设置成功表示竞争到了锁,因此非公平获取锁意味着同时获取锁资源时会存在竞争关系,不能满足先到先获取的原则。如果将state以CAS方式从0设置为1失败时,会调用模板方法acquire()
,已知acquire()
方法会调用tryAcquire()
方法,而NonfairSync
的tryAcquire()
方法会调用其父类Sync
的nonfairTryAcquire()
方法,下面看一下其实现。
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 当前线程如果是获取到锁资源的线程,则将state字段加1
// 当前线程如果不是获取到锁资源的线程,则返回false然后加入同步队列
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()
方法中主要是对获取锁资源的线程进行判断,如果当前线程就是已经获取到锁资源的线程,那么就会将state加1,因为每次都是将state加1,所以可以重复获取锁资源。
接下来再看一下公平获取锁机制的FairSync
的实现,首先FairSync
的lock()
方法会直接调用模板方法acquire()
,并已知在acquire()
方法中会调用tryAcquire()
方法,所以这里直接看FairSync
的tryAcquire()
方法的实现。
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()
方法与NonfairSync
的不同在于当state为0时多了一个hasQueuedPredecessors()
方法的判断逻辑,即判断当前的同步队列中是否已经有正在等待获取锁资源的线程,如果有,则返回true,因此公平获取锁意味着绝对时间上最先请求锁资源的线程会最先获取锁,以及等待获取锁资源时间最长的线程会最优先获取锁,这样的获取锁机制就是公平的。
现在最后分析一下ReentrantLock
的解锁逻辑。无论是非公平获取锁机制还是公平获取锁机制,如果重复对锁资源进行了n次获取,那么成功解锁就需要对锁资源进行n次释放,前(n - 1)
次释放锁资源都应该返回false。ReentrantLock
的unlock()
方法会直接调用AbstractQueuedSynchronizer
的模板方法release()
,并已知在release()
方法中会调用tryRelease()
方法,这里调用的是Sync
实现的tryRelease()
方法,如下所示。
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);
}
setState(c);
return free;
}
tryRelease()
方法中每成功释放一次锁资源,就会将state减1,所以当state为0时,就判断锁资源被全部释放,即释放锁资源成功。
总结
重入锁的定义是:已经获取锁资源的线程可以重复对锁资源进行获取。
ReentrantLock
是重入锁,内部有三个自定义同步器,分别为Sync
,NonfairSync
和FairSync
,其中NonfairSync
和FairSync
能分别提供非公平获取锁机制和公平获取锁机制,具体使用哪一种获取锁机制,需要在ReentrantLock
的构造函数中指定。
非公平锁的定义是:同时获取锁资源时会存在竞争关系,不能满足先到先获取的原则。
公平锁的定义是:绝对时间上最先请求锁资源的线程会最先获取锁,以及等待获取锁资源时间最长的线程会最优先获取锁,这样的获取锁机制就是公平的。