带你看看Java-AQS同步器 源码解读2-独占锁解锁

Java-AQS同步器 源码解读独占锁解锁

  1. Java-AQS同步器 源码解读1-独占锁加锁

  2. Java-AQS同步器 源码解读2-独占锁解锁

  3. Java-AQS同步器 源码解读3-共享锁

  4. Java-AQS同步器 源码解读4-条件队列上

今天继续在昨天的源码分析嗷嗷!!!
昨天 我们看了加锁的代码,今天我们来看下解锁的代码,解锁的入口又怎么看呢?

独占锁解锁

解锁相对加锁而言 简单了很多

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 方法里面,找到下个合适的节点 去唤醒一个等待的线程

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值