前言
Reentrantlock是可重入的互斥锁,具有与Synchronized相同的功能,但是却比Synchronized更加灵活。
Reentrantlock底层基于AbstractQueuedSynchronizer实现,AbstractQueuedSynchronizer抽象类定义了一套多线程访问共享资源的同步模板,解决了实现同步器时涉及的大量细节问题,只有少量细节需要自己设定。(AbstractQueuedSynchronizer为加锁和解锁过程提供了同步的模板方法)。
Reentrantlock结构构成
Reentrantlock实现了Lock接口,Reentrantlock具有内部类Sync、NonfairSync、FairSync,其中Sync继承于AbstractQueuedSynchronizer,Sync实现了释放资源的细节,NonfairSync是Sync的子类,实现锁的非公平模式,FairSync也是Sync的子类,实现了锁的公平模式。
源码剖析
了解了Reentrantlock的整体结构,接下来我们一个块一个块地去分析。
首先我们学习一下Lock接口的源码
public interface Lock {
// 获取 - 不可中断
void lock();
// 获取 - 可以中断
void lockInterruptibly() throws InterruptedException;
// 获取锁 - 可以返回获取锁是否成功
boolean tryLock();
// 获取锁(可以设置时间) - 可以中断
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
// 释放锁
void unlock();
// 条件变量
Condition newCondition();
}
/*
这里的中断指的是如果当前线程获取锁但是未获取到锁,我们可以使用中断方法中断获取锁,而不会使它陷入阻塞状态。
*/
AQS部分解析
AQS中有一个CLH队列,当获取锁失败时会将线程加入CLH队列,当一个锁是非公平锁时,当前线程获取锁时会和队列的首部线程争抢,成功则设置当前线程为获取锁线程,失败则加入队列;当一个锁是公平锁时,如果队列为空,则当前线程获取锁,如果队列不为空,则一定是队列首部线程获取到锁,将当前线程加入CLH队列
Sync源码
在Reentrantlock中有一个Sync对象,当我们使用的锁为非公平锁时,可以利用多态的特性,使其指向NonfairSync,同理,当我们使用的锁为公平锁的时候,我们可以将其指向FairSync。又因为Sync继承了AbstractQueuedSynchronizer,AbstractQueuedSynchronizer提供了加锁和解锁过程的模板方法,因此我们可以看出Sync在Reentrantlock中的重要性。
接下来我们就来看看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();
// 如果state == 0, 说明现在没有线程获取到锁,则当前线程尝试加锁
if (c == 0) {
// 尝试加锁
if (compareAndSetState(0, acquires)) {
// 加锁成功,设置当前线程为获取锁线程
setExclusiveOwnerThread(current);
return true;
}
}
// 如果state != 0, 但是当前线程已经是获取到锁的线程,说明当前可以多次获取锁(可重入)
else if (current == getExclusiveOwnerThread()) {
// 获取资源,state + 1
int nextc = c + acquires;
// 如果state < 0, 抛出异常
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
// 设置state的状态,此处不需要CAS,因为当前持有锁的线程只有一个,就是自己。
setState(nextc);
// 返回true
return true;
}
// 返回false
return false;
}
// 释放资源
protected final boolean tryRelease(int releases) {
// state 减去 releases
int c = getState() - releases;
// 如果当前线程不是获取锁的线程,那肯定无法释放资源,就抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
// 默认释放资源为false
boolean free = false;
// 如果state为0,说明已经释放了全部资源,设置当前持有锁的线程为null,返回true
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
// 否则说明没有释放全部资源(重入锁),返回false
setState(c);
return free;
}
// 创建条件变量
final ConditionObject newCondition() {
return new ConditionObject();
}
}
我们可以发现,Sync实现了非公平获取锁的方法,这是给子类NonfairSync用的,但是却没有实现公平获取锁的方法,需要子类FairSync自己实现。另外Sync还实现了释放资源的方法。
释放资源的流程:
- 判断当前线程是否是持有锁的线程,是就往下执行,不是则抛出异常
- 得到state减1的值,之后判断state是否等于0,等于0设置持有锁线程为null,返回true表示释放资源成功,不等于0就设置state为state-1,返回false,代表释放资源失败(重入锁)。
这是释放资源的流程图
说完了Sync,那么我们接下来说一下NonfairSync
NonfairSync
NonfairSync是Sync的子类,用于非公平获取资源
// NonfairSync的源码
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
// 加锁
final void lock() {
// 执行CAS操作
if (compareAndSetState(0, 1))
// CAS操作成功,设置当前线程为持有锁线程
setExclusiveOwnerThread(Thread.currentThread());
else
// 失败执行AQS获取锁模板流程
acquire(1);
}
// 获取资源,调用父类Sync实现的非公平获取资源方法
protected final boolean tryAcquire(int acquires) {
// 直接使用父类的非公平获取资源
return nonfairTryAcquire(acquires);
}
}
// AQS中的acquire(int arg)方法
public final void acquire(int arg) {
// 再执行一次获取资源方法,成功就结束,失败就将当前线程加入CLH队列
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
/*
我们看一下父类Sync实现非公平获取资源的细节
// 非公平获取资源
final boolean nonfairTryAcquire(int acquires) {
// 获取当前线程
final Thread current = Thread.currentThread();
// 获取当前状态
int c = getState();
// 如果state == 0, 说明现在没有线程获取到锁,则当前线程尝试加锁
if (c == 0) {
// 尝试加锁
if (compareAndSetState(0, acquires)) {
// 加锁成功,设置当前线程为获取锁线程
setExclusiveOwnerThread(current);
return true;
}
}
// 如果state != 0, 但是当前线程已经是获取到锁的线程,说明当前可以多次获取锁(可重入)
else if (current == getExclusiveOwnerThread()) {
// 获取资源,state + 1
int nextc = c + acquires;
// 如果state < 0, 抛出异常
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
// 设置state的状态,此处不需要CAS,因为当前持有锁的线程只有一个,就是自己。
setState(nextc);
// 返回true
return true;
}
// 返回false
return false;
}
*/
我们说一下非公平获取资源的方式:
- 判断state是否等于0,等于0就尝试加锁,加锁成功就设置当前线程为持有锁的线程,返回true,否则往下执行
- 判断当前线程和获取锁的线程是不是同一个线程,是同一个线程,就让state+1,返回true,否则返回false
FairSync 解析
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
// 执行AQS获取锁模板函数
final void lock() {
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// state为0,之后判断当前线程是不是队列被唤醒的线程
if (c == 0) {
// 是队列被唤醒的线程,尝试加锁
// hasQueuedPredecessors()返回false说明当前线程是队列的头部线程,就尝试获取锁
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
// 加锁成功就设置当前线程为持有锁的线程,返回true
setExclusiveOwnerThread(current);
return true;
}
}
// 当前线程和持有锁的线程是同一个线程,就让state+1,设置state,返回true,否则返回false
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
// 设置state
setState(nextc);
return true;
}
return false;
}
}
FairSync是公平获取锁,获取资源流程和NonfairSync获取资源的流程很相似,只是加了一个判断,判断如果当前线程不在队列就加入队列,如果当前线程在队列就判断当前线程的前面还有没有线程。
Reentrantlock
最后我们说一下Reentrantlock的源码
// Reentrantlock的部分源码
public class ReentrantLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = 7373984872572414699L;
// Sync对象
private final Sync sync;
// 默认为非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
// 设置锁为公平锁还是非公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
// 获取锁
public void lock() {
sync.lock();
}
// 获取锁 - 可以中断
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
// 获取锁 - 返回获取锁成功还是失败
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
// 获取锁 - 可以设置时间,可以中断
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
// 释放锁
public void unlock() {
// 使用Sync的release方法
sync.release(1);
}
// 条件变量
public Condition newCondition() {
// 使用Sync的创建条件变量方法
return sync.newCondition();
}
}
Reentrantlock有两个构造方法,无参的构造方法为非公平锁,有参的构造方法可以设置公平锁,Reentrantlock锁的实现重点靠里面的Sync对象,Sync继承了AQS,又有两个子类,使用Sync对象可以很好的实现功能。
结尾
最后我们说一下与Synchronized相比Reentrantlock有以下几个特性
- 可重入
- 可中断
- 可以设置超时时间
- 可以设置为公平锁
- 锁的粒度更小,功能更灵活
相信学习了Reentrantlock的结构和内部源码,我们对于它的使用肯定可以更加得心应手。