可重入互斥锁与使用synchronized
方法和语义访问的隐式监视器锁具有相同的基本行为和语义,但具有扩展功能。
ReentrantLock
由上次成功锁定但是尚未解锁的线程拥有。当另一个线程不拥有锁时,调用lock的线程将返回并成功获取锁
ReentrantLock
实现了公平锁和非公平锁,由构造函数的入参控制:
- 当入参为true时,为公平锁,锁倾向于对等待时长最长的线程授权使用公平锁。多个线程访问公平锁一般而言会比非公平锁慢,但是在获取锁和不出现饥饿的时间差异较小。锁的公平性并不能保证线程调度的公平性。因此所用公平锁的许多线程之一可能连续多次获取锁。另外,使用未计时的
tryLock
方法不遵守公平设置。即使其他线程在等待,如果锁可用,他也会成功
该锁最多支持同一个线程2147483647个递归锁。该类的序列化与内置锁的行为方式相同:反序列化的锁处于解锁状态,不管器序列化时的状态如何
具体实现
其内部对AQS做了两层实现:
- 第一层为通过
Sync
抽象类继承AQS,实现基础的获取与释放 - 第二层为定义公平锁和非公平锁具体实现lock等方法
Sync
Sync
抽象类实现了一些基础方法,以及非公平锁的获取。使用AQS状态来表示锁的持有次数。
Sync实现的基础方法有:tryRelease
和isHeldExclusively
。tryRelease
:
//返回当前同步器是否空闲。入参release为1
protected final boolean tryRelease(int releases) {
//使用state记录同步器的持有次数,每次释放state-1
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;
}
isHeldExclusively
:
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();
}
问题:
tryRelease
为什么要判断当前同步器的持有者是否为当前线程- 如果判断为什么不用
!isHeldExclusively
方法
Sync
定义了lock
接口。实现了非公平锁的获取方法。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;
}
}
//如果被占用了,判断占用线程是否为当前线程。如果是,那么设置同步器状态+1;如果不是,那么当方法失败
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;
}
可以看到重入锁的实现就是对判断持有锁的线程是否为当前线程,如果是,修改锁的状态。
Sysnc
没有实现AQS定义的tryAcquire
方法。而ReentrantLock
是独占锁,所以tryAcquireShared
和tryReleaseShared
方法也不需要实现。那么tryAcquire
就需要Sync
的两个子类NonfairSync
和FairSync
来实现。
NonfairSync
先明确非公平锁的定义:每个线程获取锁的顺序是随机的,并不遵循先来先得的规则,任何线程在某时刻都有可能直接获取到锁。
那么其tryAcquire
的实现必然就是Sync
中nonfairTryAcquire
方法的实现。然而nonfairTryAcquire
可能获取失败,从锁的获取来讲,获取不到的话,应该阻塞当前线程,直到再次有锁释放,参与锁的竞争。那么其应该实现了Sync
的lock
方法:
final void lock() {
//首先尝试获取锁
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
//获取不到则调用acquire方法,走一般的同步器获取流程
acquire(1);
}
在竞争不激烈的情况下,非公平锁抢锁容易成功;如果在竞争激烈的情况下,acquire
将当前线程加入同步队列,那么非公平锁的效率和公平锁的效率应该是一样的。
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;
}
公平锁在抢锁时,通过判断是否存在前继节点保证先申请锁的线程先获取锁。那么为了保证前继节点获取到锁,其lock方法,相对NoFairSync
的lock
方法应该直接调用acquire
方法,避免当前线程直接抢到锁。lock
实现:
final void lock() {
acquire(1);
}
ReentrantLock
在实例化时通过入参fair
来确定使用FairSync
还是NonfairSync
,默认为NonfairSync
。ReentrantLock
实现的Lock
的几个接口都是对同步器的调用。其中需要注意的是tryLock()
接口。
tryLock
接口的语义为仅在空闲时获取锁。如果锁可用,则立即获取锁并返回true;如果锁不可用则方法立即返回false。所以tryLock
接口应该直接去判断锁的状态,然后获取锁。其逻辑和非公平锁的tryAcquire
应该是一致的,即调用Sync
的nonfairTryAcquire