ReentrantLock 源码分析
- 简介
- Lock接口提供的功能
- 公平锁和非公平锁
-
- 公平锁
- 非公平锁
- 公平锁和非公平锁性能对比
简介
ReentrantLock是Java SE 5新增的jdk层面实现可重入锁的功能, 提供了与synchronized类似的同步功能,只是在使用时需要显示的获取和释放锁。虽然缺少了隐私获取和释放锁的便捷性,但是缺拥有了锁获取和释放的可操作性、可中断的获取锁以及超时获取锁等多种synchronized关键字不具备的同步特性。同时也提供了获取和释放锁的灵活性。
Lock接口提供的功能
ReentrantLock实现了Lock接口,因此分析ReentrantLock之前有必要了解下Lock接口定义了哪些基本操作。具体实现参考AQS一文:
Lock接口 API
方法名称 | 描述 |
---|---|
void lock() | 获取锁,调用该方法当前线程将会获取锁,当获取锁后从该方法返回 |
void lockInterruptibly() throws InterruptedException | 可中断的获取锁,和lock()方法的不同之处在于该方法会响应中断,即在锁的获取中可以中断当前线程 |
boolean tryLock() | 尝试非阻塞的获取锁,调用该方法后立即返回,如果能够获取则返回true,否则返回false |
boolean tryLock(long time, TimeUnit unit) throws InterruptedException | 超时的获取锁,当前线程在以下三种情况下会返回: 1. 当前线程在超时时间内获得了锁 2. 当前线程在超时时间内被中断 3. 超时时间结束,返回false |
void unlock() | 释放锁 |
Condition newCondition() | 获取等待通知组件,该组件和当前的锁绑定,当前线程只有获得了锁,才能调用该组件的wait()方法,而调用后,当前线程将会释放锁 |
公平锁和非公平锁
ReentrantLock提供了两种锁特性:公平锁和非公平锁,公平锁和非公平锁的区别是:
如果在绝对时间上,先对锁进行获取的请求一定先被满足,那么这个锁是公平的,反之,是不公平的。公平的获取锁,也就是等待时间最长的线程最优先获取锁,也可以说锁获取是顺序的。ReentrantLock提供了一个构造函数,能够控制锁是否是公平的。
事实上,公平的锁机制往往没有非公平的效率高,但是,并不是任何场景都是TPS作为唯一的指标,公平锁能够减少“饥饿”发生的概率,等待越久的请求越是能够到优先满足。
代码实现:
/**
* lock锁的同步控制,子类有公平和非公平两个版本。
* 该抽象锁同步器主要提供了尝试获取(非公平实现)和尝试释放锁等功能
* /
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;
}
}
// 通过判断当前线程是否为获取锁的线程来决定获取操作是否成功,如果是获取锁的线程
// 再次请求,则将同步状态值进行增加并返回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;
}
// 如果该锁被获取了n次,那么前(n-1)次tryRelease(int releases)方法必须返回false,而只有
// 同步状态完全释放了,才能返回true。可以看到,该方法将同步状态是否为0作为最终释放
// 的条件,当同步状态为0时,将占有线程设置为null,并返回true,表示释放成功。
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;
}
// 判断是否当前线程独占锁
protected final boolean isHeldExclusively() {
return getExclusiveOwnerThread() == Thread.currentThread();
}
final ConditionObject newCondition() {
return new ConditionObject();
}
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}
final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
}
final boolean isLocked() {
return getState() != 0;
}
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
setState(0); // reset to unlocked state
}
}
公平锁
/**
* 继承Sync, 并实现公平锁
* /
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) {
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;
}
}
非公平锁
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
// 阻塞获取锁, 当前线程优先去获取锁,获取失败在通过队列的方式获取
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
// 非公平的方式尝试获取锁
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
公平锁和非公平锁性能对比
在《java并发编程的艺术》书中对公平锁和非公平锁做了测试,其中公平锁的耗时是非公平锁的94.3倍,因此公平锁虽然保证了锁的获取按照FIFO原则,而代价是进行大量的线程切换。非公平性锁虽然可能造成线程“饥饿”,但极少的线程切换,保证了其更大的吞吐量。