您的位置:首页 > 编程语言 > Java开发

JDK1.8源码解析之ReentrantLock(可重入锁)

2019-07-12 11:57 423 查看
版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://blog.csdn.net/m0_37148920/article/details/95523499

ReentrantLock源码解析

  • 前言
    在学习阻塞队源码的时候,比如ArrayBlockingQueue、LinkedBlockingQueue、CyclicBarrier、SynchronousQueue频繁的遇到ReentrantLock,故先把ReentrantLock原理了解,是学习阻塞队列的基础。对Doug Lea大师崇拜。

  • 类继承关系

    有一个特别重要的抽象静态内部类Sync,这个类实现了AbstractQueueSynchronizer(提供锁模板,这篇文章中用到时候在分析)。

  • ReentrantLock有两种模式,1 公平锁 2 公平锁

public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();// 初始化的时候可以指定,不指定话默认是非公平模式
}

非公平锁

static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
final void lock() {// 重写Sync的lock方法,自定义非公平锁的实现逻辑,父类相当与模板,模板实现了大部分的方法,子类只需实现自己的逻辑。
if (compareAndSetState(0, 1)) // 这里是体现非公平锁的关键,申请锁的时候先直接尝试修改AQS的锁标志(非常的强硬)
setExclusiveOwnerThread(Thread.currentThread());// 成功修改锁标志位 则设置当前独占锁的线程
else
acquire(1);// 这里走的是AQS的模板方法 尝试获取
}

protected final boolean tryAcquire(int acquires) { // 尝试获取锁  NonfairSync类定义
return nonfairTryAcquire(acquires);
}
}

来看看AQS的acquire

public final void acquire(int arg) {
if (!tryAcquire(arg) && // 执行子类自定义的方法
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))// 申请失败情况下 将当前线程添加到对列中
selfInterrupt();
}

来看看NonfaireSync的tryAcquire

protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState(); // 获取AQS锁状态
if (c == 0) {// 当前锁处于空闲状态
if (compareAndSetState(0, acquires)) {// unsafe设置锁状态
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {// 这里体现了ReentrantLock可重入的性质,同一个线程可以多次获取锁
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;// 尝试获取锁失败
}

所获取失败,则会将当前线程加入到阻塞队列,来看看AQS的acquireQueued 是如何实现的?

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)) {// 等待队列的head释放了锁
setHead(node);// 将当前节点设置成head
p.next = null; // help GC // 自己的next指向自己方便gc回收
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&// 获取锁失败后开始进入真正的阻塞
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}

来看看AQS的shouldParkAfterFailedAcquire是如何实现的?

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
// 只有当前节点的前驱节点的状态是signal的才可以parking,就是告诉前驱我准备好了,你执行完记得唤醒我
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;// 跳过被取消的前驱节点,找到一个离自己最近的非取消的节点
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE.  Indicate that we
* need a signal, but don't park yet.  Caller will need to
* retry to make sure it cannot acquire before parking.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}

接下来再看看parkAndCheckInterrupt 这个方法就是用unsafe的park,使当前线程阻塞,等待unpark。
到此整个获取锁的过程结束了,我的天感觉脑子不够用了。
既然这里park了,那在哪里unpark这个线程呢?来看看ReentrantLock 的unlock,所以在使用lock的地方必须要以unlock结束,lock几次就需要unlock几次,将AQS的state恢复至0,要不然等待的线程永远得不到唤醒。

public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0) // h不等于null 说明等待队列不为空
unparkSuccessor(h);// 唤醒后继节点
return true;
}
return false;
}
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;// 直到AQS状态为0,才算完成锁的释放
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}

公平锁
了解了非公平锁,公平锁就超级简单了,唯一不同的就是tryAcquire了,来看看?

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;
}

ReentrantLock还有一个强大的特性,一个lock对象可以创建多个条件,每个条件也是可以阻塞的,这样就使ReentrantLock更加灵活,而Synchronized只能对 3ff8 一个类或者一个对象加锁。下面来看看Condition。
Condition是一个接口

总共只有两类方法,await类的方法是将任务阻塞到条件队列中,signal方法是将任务从条件队列中转移到lock的等待队列中。
AQS中的内部类ConditionObject实现了Condition接口,先看成员变量,

/**条件队列的第一个等待线程*/
private transient Node firstWaiter;
/** 条件队列的最后一个等待线程 */
private transient Node lastWaiter;

条件队列就是一个单向链表,链表中的节点用的依旧是AQS中的节点,创建新节点时,将新节点设置为lastWaiter的next,完成链接。

public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();// 将任务添加到条件队列的尾部
int savedState = fullyRelease(node);// 这里将AQS锁的状态清除掉,savedState是保存await之前node的状态,因为在唤醒之后需要执行unlock操作,这里也体现了可重入锁,
int interruptMode = 0;
while (!isOnSyncQueue(node)) {// 只有await获得signal后才会将node移动到lock的等待队列中。
LockSupport.park(this);// 执行阻塞,等待signal 的transfer
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)// 走到这里就说明node已经从条件队列中转移到等待队列中,现在可以尝试获取lock锁
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}

接下来看看AQS ConditionObject 的signal,signal的目的是将条件队里中的头结点转移动等待队列中。

public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) && (first = firstWaiter) != null);
}
final boolean transferForSignal(Node node) {
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
Node p = enq(node);// 把任务添加到等待队列
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);// 唤醒阻塞线程,回到await方法,继续执行,接下来进行尝试获取lock锁,这里就和等待队列中尝试获取锁一样了。
return true;
}

condition这块核心的部分分析完了,其他的方法,基本上没啥难点了,下一篇开启阻塞队列之旅了。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: