1,前言
重入锁ReentrantLock
,顾名思义就是支持重进入的锁:它表示该锁能够支持一个线程对资源的重复加锁。初次之外,该锁还支持获取锁时的公平和非公平选择
2,ReentrantLock
重进入是指任意线程在获取锁之后能够再次获取该锁而不会被锁阻塞,该特性的实现需要解决以下两个问题:
- 线程再次获取锁:锁需要去识别获取锁的线程是否为当前占据锁的线程,如果是则再次成功获取
- 锁的最终释放:线程重复n次获取了锁,随后在第n次释放该锁后,其他线程能够获取该锁。该锁的最终释放要求锁对于获取进行计数自增,计数表示当前锁被重复获取的次数,而每当锁被释放时,计数自减,当计数为0时表示锁成功释放
ReentrantLock
是通过AQS实现自定义同步器来实现锁的获取和释放,以非公平性实现为例,其源代码如下:
public class ReentrantLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = 7373984872572414699L;
/** Synchronizer providing all implementation mechanics */
private final Sync sync;
......
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
abstract void lock();
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
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");
setState(nextc);
return true;
}
return false;
}
......
}
......
}
该方法增加了再次获取同步获取同步状态的逻辑:即当判断如果该锁已经被获取之后,在下一个if
分支中,判断试图获取的线程是否是持有锁的线程!
else if (current == getExclusiveOwnerThread()) {
......
}
同样的,每当线程重复获取该锁,就会有相应的释放锁的动作,下面是ReentrantLock
的tryRelease
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
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;
}
}
如果该锁被获取了 n 次,那么前(n-1)次 tryRelease(int releases)
方法必须返回 false
,而只有同步状态完全释放了,才能返回 true
。当同步状态为0时,将占有线程设置为null
,并返回true
,表示释放成功。
3,公平与非公平获取锁的区别
公平与非公平是针对获取锁而言的:例如获取锁的顺序是由线程何时进入同步队列中决定的,那么它就是公平获取,也就是FIFO;反之
上面提及到的nonfairTryAcquire(int acquires)
,从命名不难看出,是非公平的获取锁,只要CAS成功设置同步状态,就被认为线程已经获取了该锁。而公平锁则不一样,它需要先判断线程在同步队列中此时有无前驱节点,若有,则判断此时有其他线程排在其前面等待获取该锁。其源代码如下:
public class ReentrantLock implements Lock, java.io.Serializable {{
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) {
//这里的hasQueuedPredecessors()表示判断是否有前驱节点,没有的话就返回false
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;
}
}
}
默认实现
实际上观察ReentrantLock
源代码,可以看到其默认实现是非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
相较于公平锁,非公平锁开销更小。公平锁保证了锁的获取符合FIFO原则,但是其代价是大量的线程切换。非公平锁虽然可能造成线程“饥饿”,但是其极少的线程切换,保证了更大的吞吐量。