源码分析
1.unlock()方法实际是调用父类AQS的release()方法
public void unlock() {
sync.release(1);
}
2.release()方法首先又调用了tryRelease(1)方法,这个方法依旧由AQS的子类Sync类来实现,见2.1分析
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
2.1 tryRelease()方法主要是对锁状态值进行还原,并且释放占有锁的线程,并将解锁的结果返回
protected final boolean tryRelease(int releases) {
//将锁的状态值-1
int c = getState() - releases;
//如果拥有锁的线程不是现在的运行线程,则抛出异常,避免未lock情况下unlock
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
//锁空闲标识,默认否
boolean free = false;
//如果锁的状态值恢复到0,则将锁空闲标识改为true,并将锁的所属线程设置为null
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
//将锁的状态值同步到AQS里
setState(c);
return free;
}
这里会有两种情况
第一种情况不是重入锁,释放锁后,free状态返回true,再继续后续的唤醒等待线程操作。
第二种情况如果是重入锁释放锁,则free状态还是false,只是state的值递减1,后面唤醒锁的操作不会执行
2.2 上面2.1分析的方法如果返回false则解锁过程结束,如果返回true则继续接第2步if方法体里代码进行唤醒等待线程
public final boolean release(int arg) {
if (tryRelease(arg)) {
//拿到头节点
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
如果头节点不是空,且头节点的状态不是0,执行unparkSuccessor(h),入参为头节点,进行唤醒操作;
private void unparkSuccessor(Node node) {
//头节点状态
int ws = node.waitStatus;
//如果头节点的状态是-1将状态改为0
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
//头节点的后继节点
Node s = node.next;
//如果后继节点是null,或者状态大于等于0,说明后继节点取消了,为无效节点
if (s == null || s.waitStatus > 0) {
s = null;
//从尾节点往前循环,找到符合状态为小于等于0的节点,并赋给s
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
//s不为null时,唤醒节点里的线程
//唤醒的线程会从挂起的地方开始执行,也就是继上一篇文章《ReentrantLock源码解析(加锁)》第5.2.2节第5行继续执行。
if (s != null)
LockSupport.unpark(s.thread);
}
这个方法做了几步操作有健壮性的考虑,
1.将头节点的状态改为0,避免重复循环
2.取到头节点的后继节点
3.校验后继节点的状态是否可以被唤醒,如果后继节点是空的,或者状态大于0(线程被取消或者中断),则从尾节点开始循环找出最前面(FIFO队列特点)可以被唤醒的线程。
4.唤醒上面2,3最终确定的等待线程
----------------- 文章如有问题,请下方回复指出,感谢查阅😁 -----------------