- Java-AQS同步器 源码解读<一>独占锁加锁
- Java-AQS同步器 源码解读<二>独占锁解锁
- Java-AQS同步器 源码解读<三>共享锁
- Java-AQS同步器 源码解读<四>-条件队列上
- Java-AQS同步器 源码解读<五>-条件队列下
今天继续在昨天的源码分析嗷嗷!!!
昨天 我们看了加锁的代码,今天我们来看下解锁的代码,解锁的入口又怎么看呢?
独占锁解锁
解锁相对加锁而言 简单了很多
-release方法
我们还是拿ReentrantLock重入锁解锁举例去看下怎么做的,然后一步一步的进入
public void unlock() {
sync.release(1);
}
上面是ReentrantLock的解锁方法,上一篇文章上我描述了sync 是ReentrantLock内部抽象方法类 继承了AQS ,那这个release 方法 我们很容易就定位到了在AQS的一个方法
/**
* AbstractQueuedSynchronizer 类中方法
*/
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
-tryRelease方法
看到这个tryRelease 看过上一篇文章的 应该心里知道 怎么回事 和加锁的时候tryAcquire是一样的,tryRelease 需要使用到此方法的类 去重写这个方法,不让回报错,因为这个方法AQS没有实现,只是抛出异常,具体为什么会这么写,上一篇文章我也提到过怎么回事,不清楚的 可以去上一篇文章中看一看
我们再去ReentrantLock类中看一看
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
abstract void lock();
//释放资源
protected final boolean tryRelease(int releases) {
int c = getState() - releases;// 算下 当前同步器中state的差值
//确保释放和加锁线程是同一个 上一篇中我也提到过
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {//如果C的值是0 说明没有线程拥有锁了
free = true;
/*设置拥有锁的线程为null 因为现在锁是没线程暂用的 如果不修改 下次别的线程去获取锁的会有这个判断
*/
setExclusiveOwnerThread(null);
}
setState(c);//修改 同步器的State 状态
return free;
}
}
很快 我们找到了tryRelease在Sync中的实现,题外说下Sync 2个子类一个非公平锁一个公平锁,区别就是在加锁,解锁其实都一样的,不信看代码结构就能看到,2个子类的解锁都没有重写 都是用了Sync类中的实现。
那我们回到上面的release方法中
/*
* 释放当前资源
* 唤醒等待线程去获取资源
*/
public final boolean release(int arg) {
if (tryRelease(arg)) {//说明释放资源成功
Node h = head;//当前队列里面的head节点
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);//通知阻塞队列里面的head的next节点去获取锁
return true;
}
return false;
}
-unparkSuccessor方法
/**
* 唤醒等待线程去获取资源
*/
private void unparkSuccessor(Node node) {
/*
* 这边判断了下如果当前节点状态小于0,更新这边的节点状态的0,是为了防止多次释放的时候 会多次唤醒,
* 因为上面的方法有个判断waitStatus不等0才会执行到这个方法里面
*/
int ws = node.waitStatus;//这边的弄得节点就是 要释放的节点 也就是当前队列的头节点
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
// 如果当前节点的next 节点 不存在或者waitStatus 大于0 说明next节点的线程已取消
if (s == null || s.waitStatus > 0) {
s = null;
//这个循环就是 从尾部节点开始往前找,找到离node节点也就是当前释放节点最近的一个非取消的节点
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
/*一开始觉得 这行判断null有点多余 因为上面去for 循环去找s 的时候 已经判断了不等于null
才可以进下面的循环赋值的 后来一想 不对,你们猜为什么?
因为 可能在循环的过程中t在赋值给s后,继续循环 虽然条件不满足,
但是这个时候已经比修改成null 了 我是这么想的哈~不知道对不对~
*/
if (s != null)
LockSupport.unpark(s.thread);// 这边就是唤醒当前线程去获取资源,
}
线程被唤醒后 就继续执行到parkAndCheckInterrupt 方法里面这边 因为就是在这边线程被阻塞的 后面就去继续尝试获取资源了
总结一下
独占锁的解锁比较简单 分为2个步骤
第一步就是去tryRelease释放资源
第二步 如果释放资源成功,就进入unparkSuccessor 方法里面,找到下个合适的节点 去唤醒一个等待的线程