文章参考:(4条消息) AQS源码探究_04 成员方法解析(释放锁、响应中断出队逻辑)_兴趣使然的草帽路飞-CSDN博客
AQS成员方法解析(释放锁的逻辑)
解锁的流程:
unlock() 解锁的方法,主要逻辑调用了release()方法
release() 真正解锁的方法,主要逻辑是调用tryRelease()尝试释放锁的方法,如果返回true则尝试释放锁成功,找到头节点引用,这里阻塞队列中的头结点已经释放了锁,判断头结点不为null和头节点后仍然有节点,如果成立,则需要唤醒后继节点。如果不成立返回false,说明没有后继节点,再往上返回true,证明当前线程已经释放了锁。
tryRelease()尝试释放锁的方法,主要逻辑是先获取当前状态变量state的值,减去参数releases后用变量c标记,然后判断当前线程是否是持有锁的线程,如果不是直接抛出异常,如果是则进入下面的逻辑,用一个布尔型的变量free标记当前线程是否已经释放了锁,判断c是否等于0,等于0则表示当前线程达到释放锁的条件,这里会将free设置为true标记当前线程已经释放锁了,还要将持有锁的线程设置为null。完成后,基于CAS更新状态变量state的值,返回free。
1.unlock释放锁方法
//位于ReentrantLock中:释放锁的方法
public void unlock() {
//调用AQS中的release方法
sync.release(1);
}
2.release真正释放锁的方法
//ReentrantLock.unlock() ->sync.release()
//调用的是AQS中实现的release()方法
public final boolean release(int arg) {
//tryRelease()尝试释放锁
//true 表示当前线程已经完全释放了锁
//false 表示当前线程尚未完全释放锁
if (tryRelease(arg)) {
//能进入if块中说明 当前线程已经释放了锁
//找到头结点引用 这时头结点已经释放了锁
Node h = head;
//条件1:h!=null成立,说明队列中的head节点已经初始化过
//条件2:h.waitStatus !=0 成立,说明当前head后面一定插入过node节点
if (h != null && h.waitStatus != 0)
//唤醒后继节点,当前持有锁的线程已经释放了锁,需要唤醒后继节点加锁
//这个if判断主要是判断head后有节点,也就是阻塞队列中还有节点
unparkSuccessor(h);
return true;
}
return false;
}
3.tryRelease尝试释放锁的方法
//位于ReentrantLock的静态内部类Sync中,尝试释放锁的方法
//true 当前线程已经完全释放锁 false当前线程尚未完全释放锁
protected final boolean tryRelease(int releases) {
//state状态变量的值相减
int c = getState() - releases;
//判断当前线程是否是持有锁的线程 条件成立 说明当前线程未持有锁 直接抛出异常
if (Thread.currentThread() !=
getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
//标记当前线程是否已经完全释放锁
boolean free = false;
// c==0 说明当前线程已经达到完全释放锁的条件
if (c == 0) {
//释放锁要做些什么??
//标记当前线程已经释放了锁
free = true;
//设置当前持有锁的线程为null
setExclusiveOwnerThread(null);
}
//基于CAS原子更新state
setState(c);
//返回free free标记的是当前线程是否已经释放锁
return free;
}
4.unparkSuccessor唤醒后继节点线程的方法
//AQS中唤醒后继节点线程的方法
private void unparkSuccessor(Node node) {
//获取当前node节点的waitStatus状态 waitStatus状态值主要有5种 0 1 -1 -2 -3
//0是初始化时的0
//1是标识线程已取消 标识当前线程处于取消状态
//-1是标识后继节点需要唤醒(标识当前节点需要唤醒他的后继节点)
//-2标识线程在等待一个条件上
//-3标识后面的共享锁需要无条件的传播(共享锁需要连续唤醒读的线程)
int ws = node.waitStatus;
//这里为什么要判断ws是否小于0? 小于0为什么要通过CAS更新node的waitStatus值为0?我们先来看下面的逻辑
if (ws < 0) //看完下面的逻辑 我们回过头看这里是要判断当前节点是否完成了唤醒后继节点的任务 如果当前进入节点的ws < 0 表示了当前节点是处于唤醒的状态 标识当前节点需要唤醒他的后继节点 并通过CAS将当前节点的waitStatus改为0
compareAndSetWaitStatus(node, ws, 0);
//获取node节点的后继节点
Node s = node.next;
//条件1 s==null
//s什么情况下才为null 1.当前节点为tail阻塞队列的尾结点时 node.next才为null
//2.当前节点入队未完成时(1.设置新节点的prev指向pred 2.CAS设置新节点为tail 3.(未完成)pred.next -> 新节点)
//需要找到可以被唤醒的节点
//条件2:s.waitStatus>0
//如果条件2成立 则说明了当前node节点的后继节点是取消状态,需要找到一个合适的可以被唤醒的节点
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;
//上面的循环是找到一个离当前node最近的一个可以被唤醒的节点,该节点可能找不到
}
//如果找到合适的且可以被唤醒的节点s,则将s节点的thread唤醒,恢复此线程的运行。如果没找到,则什么都不做
if (s != null)
LockSupport.unpark(s.thread);
}
总结
下面总结一下前面几篇文章的主要内容:
(1)AQS是Java中几乎所有锁和同步器的一个基础框架,这里说的是“几乎”,因为有极个别确实没有通过AQS来实现;
(2)AQS中维护了一个队列,这个队列使用双链表实现,用于保存等待锁排队的线程;
(3)AQS中维护了一个状态变量,控制这个状态变量就可以实现加锁解锁操作了;
(4)基于AQS自己动手写一个锁非常简单,只需要实现AQS的几个方法即可。