重入锁ReentrantLock,顾名思义,就是支持重进入的锁,它表示该锁能够支持一个线程对资源的重复加锁。除此之外,该锁的还支持获取锁时的公平和非公平性选择。
ReentrantLock虽然没能像synchronized关键字一样支持隐式的重进入,但是在调用lock()方法时,已经获取到锁的线程,能够再次调用lock()方法获取锁而不被阻塞。
ReentrantLock提供了一个构造函数,能够控制锁是否是公平的(如果在绝对时间上,先对锁进行获取的请求一定先被满足,那么这个锁是公平的,反之,是不公平的)。事实上,公平的锁机制往往没有非公平的效率高,但是,并不是任何场景都是以TPS作为唯一的指标,公平锁能够减少“饥饿”发生的概率,等待越久的请求越是能够得到优先满足。
1 实现重入
- 线程再次获取锁
锁需要去识别获取锁的线程是否为当前占据锁的线程,如果是,则再次成功获取。 - 锁的最终释放
线程重复n次获取了锁,随后在第n次释放该锁后,其他线程能够获取到该锁。锁的最终释放要求锁对于获取进行计数自增,计数表示当前锁被重复获取的次数,而锁被释放时,计数自减,当计数等于0时表示锁已经成功释放。
ReentrantLock的nonfairTryAcquire方法:
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)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
ReentrantLock的tryRelease方法:
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// 当同步状态为0时,将占有线程设置为null,并返回true,表示释放成功
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
2 公平与非公平获取锁的区别
上一小节中介绍的nonfairTryAcquire(int acquires)方法,对于非公平锁,只要CAS设置同步状态成功,则表示当前线程获取了锁,而公平锁则不同,代码如下所示:
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 唯一不同的位置为判断条件多了hasQueuedPredecessors()方法
// 即加入了同步队列中当前节点是否有前驱节点的判断,如果该
// 方法返回true,则表示有线程比当前线程更早地请求获取锁
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;
}
3 性能对比
公平性锁保证了锁的获取按照FIFO原则,而代价是进行大量的线程切换。
非公平性锁虽然可能造成线程“饥饿”,但极少的线程切换,保证了其更大的吞吐量。