ReentrantLock不像sync可以自动释放锁,它需要手动释放,否则就会出现死锁情况。
public void unlock() {
sync.release(1);
}
在release()中,通过 tryRelease() 来判断释放锁的情况。
先看tryRelease()方法
校验了当前线程是否是占用锁线程,不是会直接抛出异常
同时判断 statet状态是否为0 ,是说明是完全释放了锁,更新state的值。 不是则啥都不操作。
@ReservedStackAccess
protected final boolean tryRelease(int releases) {
// 减去释放的值
int c = getState() - releases;
// 如果当前线程不是占用锁的线程,说明当前线程并没有持锁
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
// 当前线程持有锁 ,默认是false
boolean free = false;
// 如果成立,说明完全释放锁了
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
// 更新AQS state的值
setState(c);
return free;
}
在release()方法中,获取成功进入释放锁时,需要的操作是唤醒后继节点的操作.
获取当前的state值,如果小于0,说明是-1,当前状态为Signal状态。 当前节点已经完成了唤醒后继节点的操作,更新state的值为0即可。
当s == null 表示当前节点是tail节点, >0 说明当前节点的后继节点是取消状态,说明后继节点是取消状态,需要进行的操作是唤醒一个合适的节点。
通过for循环,找到一个最佳的可以被唤醒的node进行赋值给s.
如果s != null 说明找到了节点,直接唤醒s节点即可。
private void unparkSuccessor(Node node) {
// 获取当前节点的状态
int ws = node.waitStatus;
if (ws < 0) // -1 Signal状态 因为当前节点已经完成了唤醒后继节点的任务了
// 更新为 0
node.compareAndSetWaitStatus(ws, 0);
// s 为当前节点的后继节点
Node s = node.next;
// 条件一: s == null 表示当前节点是 tail节点
// 条件二: 说明当前node 节点的后继节点是取消状态
// 成立: 说明当前node节点的后继节点是取消状态, 需要找到一个合适的可以被唤醒的节点
if (s == null || s.waitStatus > 0) {
// 查找可以被唤醒的节点
s = null;
for (Node p = tail; p != node && p != null; p = p.prev)
if (p.waitStatus <= 0)
s = p;
// 循环可以找到一个距离当前node最近的一个可以被唤醒的node, node可能找到,也可以是null
}
//
if (s != null)
// 如果找到了,就唤醒
LockSupport.unpark(s.thread);
}
回到release()中,当前的head节点 已经被初始化, 且后继节点有值,才会进行唤醒后一个节点的操作。
也可以理解为,释放锁操作在tryRelease()方法中处理,处理完成后,如果不是最后一个节点还会继续唤醒后一个节点的操作。
public final boolean release(int arg) {
// 尝试释放锁: tryRelease 返回
if (tryRelease(arg)) {
// head 什么情况下被创建出来?
// 当持锁线程未被释放,且有其他线程想要获取锁时,其他线程发现获取不了锁,且队列为空队列
Node h = head;
// 条件一成立: 说明队列中的head节点已经初始化了
// 条件二成立: 说明当前head后面一定插入过 node节点
if (h != null && h.waitStatus != 0)
// 唤醒后继节点
unparkSuccessor(h);
return true;
}
return false;
}