ReentrantLock源码解读
前情提要阅读前需对公平锁,非公平锁;可重入锁,非可重入锁;独占锁,共享锁。有一定的了解。各种锁的概念及介绍
1、ReentrantLock的类结构
他的结构非常简单,如图
graph BT
ReentrantLock --implements--> Lock
ReentrantLock --implements--> Serializable
ReentrantLock实现的锁又可以分为两类,分别是公平锁和非公平锁,分别由ReentrantLock类中的两个内部类FairSync和NonfairSync来实现。FiarSync和NonfairSync均继承了Sync类,而Sync类又继承了AbstractQueuedSynchronizer(AQS)类,所以ReentrantLock最终是依靠AQS来实现锁的。
公平锁和非公平锁继承图,因为他们的顶层父类都是AbstractOwnableSynchronizer,所以他们基于AQS都是独享锁,锁概念请查看
graph BT
FairSync内部类 --继承--> Sync --继承--> AbstractQueuedSynchronizer --继承--> AbstractOwnableSynchronizer
NonfairSync内部类 --继承--> Sync
2、ReentrantLock公平锁源码解读
2.1 构造方法源码
ReentrantLock的构造方法如下
/**
* 无参的构造方法默认构建一非公平锁
*/
public ReentrantLock() {
sync = new NonfairSync();
}
/**
* 有参的构造,
* 根据出传入的参数来构建一个公平锁或非公平锁
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
- 当调用lock.lock()时,会调用到FairSync.lock()方法,FairSync.lock()方法会调用AQS中的acquire()方法。在AQS的acquire()方法中会先调用子类的tryAcquire()方法,此时由于我们创建的是公平锁,所以会调用FairSync类中的tryAcquire()方法
2.2 公平锁创建以及加锁
// 该锁现在是独享锁,公平锁,可重入锁
ReentrantLock lock = new ReentrantLock(true);
try{
lock.lock(); // 检查是否可以得到锁会调用子类的实现的方法tryAcquire来获取
System.out.println("Hello World");
}finally {
lock.unlock(); //解锁
}
- 创建的是公平锁,所以这个时候调用的是FailSync的lock()方法。
static final class FairSync extends Sync {
final void lock() {
// 调用AQS类acquire()获取同步状态(获取锁)
acquire(1);
}
}
2.3 AQS中acquire()方法源码解读
- 在FailSync的lock()方法中就会调用到AQS的模板方法:
acquire()该方法的源码以及解读如下。最终的加锁逻辑就是在acquire()方法中实现的。acquire()的源码如下:
调用子类的具体实现-tryAcquire()方法是尝试获取同步状态,如果该方法返回true,表示获取同步状态成功;返回false表示获取同步状态失败
第1种情况: 当tryAcquire()返回true时。
这种情况表示线程获取锁成功,acquire()方法会直接结束并返回。
第2中情况: 当tryAcquire()返回false时,表示线程没有获取到锁,这时需要将线程加入到同步队列。
执行addWaiter(Node.EXCLUSIVE)方法(Node.EXCLUSIVE是独占锁;Node.SHARED是共享锁),将当前线程放入到执行队列中。
在独享锁中,addWaiter()的参数是null,addWaiter()方法返回的是当前线程的节点。
再调用acquireQueued()方法,在这个方法中,会先判断当前线程代表的节点是不是第二个节点(为什么是第二个呢?因为第一个是目前在执行的线程,所以第二个需要去阻塞获取锁,以此来执行),如果是就会尝试获取锁,如果获取不到锁,线程就会被阻塞;如果获取到锁,就会返回。
acquireQueued()方法如果返回的是true,表示线程是被中断后醒来的,此时if的条件判断成功,就会执行selfInterrupt()方法,该方法的作用就是将当前线程的中断标识位设置为中断状态。 如果acquireQueued()方法返回的是false,表示线程不是被中断后醒来的,是正常唤醒,此时if的条件判断不会成功。acquire()方法执行结束
总结:只有当线程获取到锁时,acquire()方法才会结束;如果线程没有获取到锁,那么它就会一直阻塞在acquireQueued()方法中,那么acquire()方法就一直不结束。
public final void acquire(int arg) {
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
// 调用selfInterrupt()方法重新设置中断标识
selfInterrupt();
}
2.3.1 FairSync(公平锁)的tryAcquire方法解读
公平锁得到锁的方法如下: tryAcquire 返回ture表示得到的锁,在AQS中维护了一个工作的队列,在获得锁的同时我们会发现有一个setExclusiveOwnerThread()方法,该方法用于设置改锁的独享性。
protected final boolean tryAcquire(int acquires) {
// 得到当前的线程
final Thread current = Thread.currentThread();
// 得到锁的状态
int c = getState();
// c==0 表示没有其他人得到锁
if (c == 0) {
// 因FairSync是公平锁,所以不能插队,需要判断是否有其他线程在队列
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;
}
}
// ----------------------------------------华丽的分割线---------------
// 内部类FairSync继承内部类Sync,内部类Sync继承AbstractQueuedSynchronizer
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
// 返回false说明没有线程排队
return h != t &&((s = h.next) == null || s.thread != Thread.currentThread());
}
2.3.2 FairSync(公平锁)的addWaiter(Node.EXCLUSIVE)解读
返回当前需要执行的节点。
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
// 利用CAS插入链表的最后一个节点
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 用作初始化执行线程的队列
enq(node);
return node;
}
// ----------------------------------------华丽的分割线---------------
private Node enq(final Node node) {
for (;;) {
// 最后一个节点=null 初始化一个,并让tail=head
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
// 然后接上最后把当前执行的线程接到末尾
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
2.3.3 FairSync(公平锁)的acquireQueued()源码解读
- 在执行完addWaiter()方法后,就会执行acquireQueued()方法。该方法的作用就是让线程以不间断的方式获取锁,如果获取不到,就会一直阻塞在这个方法里面,直到获取到锁,才会从该方法里面返回。
- Anchor在这里介绍线程的三个方法。interrupt 发送线程中断指令,具体什么时候中断看情况。isInterrupted检查中断状态,但不清除中断信息(只是检测是否被中断)。interrupted检查线程是否中断,且返回后会重置中断状态为未中断。
// 如果在等待时中断,则为true
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
// 阻塞的获取锁
for (;;) {
final Node p = node.predecessor();
// 上一个节点是队列的第一个节点尝试获取锁
if (p == head && tryAcquire(arg)) {
// 设置头
setHead(node);
// 其他的队列清空
p.next = null; // help GC
// 防止运行当中出错,导致的死锁
failed = false;
return interrupted;
}
// waitStatus >0 是已经停止中止的线程,不需要在队列中排队
// 只有waitStatus = -1(SIGNAL)线程才会被唤醒,
// parkAndCheckInterrupt停止当前线程并,检测中断状态,并清除中断状态等待释放锁的时候唤醒线程
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
3、unlock()释放锁源码解读
释放锁的流程
1、ReentrantLock的unlock方法会调用AQS的release() 方法进行实现
2、在release中会先尝试释放锁(因为是可冲入锁,所以需要尝试释放,不是一定会释放)
3、释放失败,就返回,释放成功就唤醒队列单中第二个需要唤醒的线程继续执行
public final boolean release(int arg) {
// 调用ReentrantLock中内部类Sync的tryRelease来实现锁的释放
if (tryRelease(arg)) {
Node h = head;
// 唤醒队列的线程waitStatus != 0 说明有队列,唤醒队列中的线程
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
3.1 tryRelease源码解读
在释放锁的过程当中,因为ReentrantLock是可重入锁和独享锁,所以释放锁时还需要释放掉独享锁锁存的线程信息。
// 返回true说明已经释放锁,不为0说明当前线程的其他方法还持有锁
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
// 不是当前线程,释放锁失败
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// c==0 说明已经完全释放掉锁,释放掉独享锁
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
3.2 unparkSuccessor源码解读
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
// 清除waitStatus的信息
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
// 队列的下一个节点的waitStatus说明线程不需要被唤醒,
// 剔除所有不需要被唤醒的线程
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
// 唤醒线程
if (s != null)
LockSupport.unpark(s.thread);
}
3、小结
经过上面的源码,我们可以看到,ReentrantLock是一个悲观锁、可重入锁、独享锁,并可以通过构造参数来指定是公平锁还是非公平锁的一个具体实现,与synchronized相比,它更具有可塑性,并且它可以通过waitStatus的状态来控制当前线程是否继续等待锁。ReentrantLock的公平锁源码已经被我们全部拔出来了,请各位看官老爷看的时候并结合自己的理解debug调试一下加深一下理解。
下面我们来看一下ReentrantLock的非公平锁源码他和公平锁的源码几乎相差无几。
4、ReentrantLock非公平锁源码解读
4.1 非公平锁的创建以及加锁
传入的参数为false创建的内部类对象是NonfairSync。
// 该锁现在是独享锁,非公平锁,可重入锁
ReentrantLock lock = new ReentrantLock(false);
try{
lock.lock(); // 检查是否可以得到锁会调用子类的实现的方法tryAcquire来获取
System.out.println("Hello World");
}finally {
lock.unlock(); //解锁
}
公平锁调用的是NonfairSync的lock方法,通过查看源码可以看到非公平锁加锁的时候会先通过CAS先查看自己是否可以获取锁。
如果获取锁则将线程加入到自己的独享锁中保存当前线程。
如果不能获取锁,则像公平锁一样去调用父类的acquire(1) 将锁加入线程队列当中。但是在获取锁是调用的是NonfairSync的tryAcquire方法,然后去调用父类的Sync来尝试获取锁。
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
4.2 非公平锁获取锁的源码解读
通过下面的源码,我们可以看到非公平锁的源码和公平锁的源码只相差了一个 !hasQueuedPredecessors() 方法,该方法是判断当前线程是不是队列当中的第一个,也就是可以插队来执行。
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;
}
Synchronized和ReentrantLock 的区别
| synchronized | ReentrantLock | |
|---|---|---|
| 锁实现机制 | 对象头监视器模式 | 依赖AQS |
| 灵活性 | 不灵活 | 支持响应中断、超时、超市获取锁 |
| 释放锁的形式 | 自动释放 | 显示调用unlock() |
| 支持锁类型 | 非公平锁 | 公平锁&非公平锁 |
| 条件队列 | 单条件队列 | 多个条件队列 |
| 是否可重入 | 支持 | 支持 |