前言
在上一章之前已经详细描述过AQS,ReentrantLock则是在AQS的基础上实现的,如果不了解AQS的同学可以先看看 JUC:并发基础之AQS。
可重入锁,见名知意,和synchronized一样当前线程可重复获取锁。与synchronized不同的是,ReentrantLock支持更多高级功能,如可实现公平锁、非公平锁,可响应中断,以及单个锁可绑定多个条件等。
Lock接口
ReentrantLock实现了Lock接口,我们先看看Lock接口的定义。
Lock接口定义了6个方法:
- lock():获取锁
- lockInterruptibly():可响应中断方式获取锁
- tryLock():尝试获取锁,失败直接返回
- tryLock(long time, TimeUnit unit):尝试获取锁,到时间仍获取不到则返回。
- unlock():解锁
- newCondition():获取条件对象
ReentrantLock
类图
可以看到,ReentrantLock内有三个内部类。 分别是Sync、NonfairSync、FairSyn,其中NonfairSync、FairSyn继承自Sync,Sync类继承自AbstractQueuedSynchronizer抽象类。
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();
// 调用aqs的getState方法
int c = getState();
// 如果资源没有被占用
if (c == 0) {
// 如果csa方式获取资源成功
if (compareAndSetState(0, acquires)) {
// 设置当前独占式获取锁线程为当前线程
setExclusiveOwnerThread(current);
// 返回ture
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;
}
// 尝试释放资源
protected final boolean tryRelease(int releases) {
// 取得需要的资源
int c = getState() - releases;
// 如果当前线程不是独占式占有资源的线程
if (Thread.currentThread() != getExclusiveOwnerThread())
// 直接抛异常
throw new IllegalMonitorStateException();
boolean free = false;
// 如果释放资源后的资源为0
if (c == 0) {
// 返回释放资源成功
free = true;
// 设置当前独占式获取资源线程为null
setExclusiveOwnerThread(null);
}
// 设置资源数
setState(c);
return free;
}
protected final boolean isHeldExclusively() {
// 返回当前线程是否是获取资源线程
return getExclusiveOwnerThread() == Thread.currentThread();
}
// conditionObject用于等待/唤醒,AQS里有讲
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类中主要定义了尝试非公平式获取资源的方法。以及尝试释放资源的方法。
- 尝试非公平式获取资源(nonfairTryAcquire):
- 判断当前资源是否被占用
- 没有被占用则尝试获取资源
- 判断当前资源是否被当前线程占用
- 是则修改资源的占用数
- 判断当前资源是否被占用
- 尝试释放资源(tryRelease):
- 计算释放资源后的资源
- 判断当前线程是否独占资源
- 不是则抛异常
- 如果释放资源后的资源为0则返回值为true,同时将占用资源线程设置为null
- 设置资源
可以看到,如果该锁被获取了n次,那么前(n-1)次tryRelease方法必须返回false,而只有同步状态完全释放了,才能返回true。
NonfairSync
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* 实现了Sync的抽象方法lock
*/
final void lock() {
// CAS方式获取设置资源
if (compareAndSetState(0, 1))
// 设置成功则直接设置当前线程为独占资源线程
setExclusiveOwnerThread(Thread.currentThread());
else
// 调用AQS的acquire方法
acquire(1);
}
// 直接调用Sync的非公平式获取资源方法
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
总结一下非公平式lock:
- 首先CAS方式尝试获取资源,抢占资源成功则设置当前独占线程为当前线程。
- 如果失败则调用AQS的acquire方法
- acquire方法最终会调用到Sync的nonfairTryAcquire,再尝试获取一次资源。
也就是说,非公平锁在进入到队列之前会有两次抢占机会。
FairSync
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
//lock直接调用aqs的acquire方法
final void lock() {
acquire(1);
}
/**
* acquire方法会第一个调用的方法就是tryAcquire
* 上章AQS有讲
*/
protected final boolean tryAcquire(int acquires) {
// 获取当前线程
final Thread current = Thread.currentThread();
// 获取当前资源
int c = getState();
// 如果资源没有被占用
if (c == 0) {
// 与非公平锁不同的是,此处多了hasQueuedPredecessors判断。
// 如果hasQueuedPredecessors返回为true,则标识有其他线程先于当前线程获取锁。
// 如果没有,则CAS方式获取锁。
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");
// 因为获取锁线程就是当前线程,所以不需要使用CAS方式直接set。
setState(nextc);
return true;
}
return false;
}
}
- 公平式尝试获取资源(tryAcquire):
- 判断当前资源是否被占用
- 如果没有被占用则判断是否有其他线程先于当前线程获取锁
- 如果没有则CAS方式获取锁
- 判断占用资源是否是当前线程
- 是则修改资源的占用数
- 判断当前资源是否被占用
可以看到,公平锁在非公平的基础上,加上了一个hasQueuedPredecessors判断是否有其他线程先于当前线程获取锁,这一步是保证公平的关键。我们来看看怎么实现的。
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
- 如果h==t,则证明队列为空,无前驱节点,返回false表示没有其他线程先于当前线程。
- 如果h!=t,则看头节点的后继节点是否为null,如果为null返回true,表示有其他线程先于当前线程。(见AQS的enq方法,compareAndSetHead(node)完成,还没执行tail = head语句时,此时tail=null,head=newNode,head.next-null)
- 如果h!=t,则看头节点的next是否不为null,则判断是否是当前线程,如果是返回false,否则有前驱节点,返回true。
总结
在掌握了AQS后,再来分析ReentrantLock的源码,就会非常简单,因为ReentrantLock的绝大部分操作都是基于AQS类的。所以,进行分析时要找准方向,就会事半功倍。谢谢各位同学观看~