AQS源码探究_04 成员方法解析(释放锁、响应中断出队逻辑)

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: AQS成员方法解析(释放锁逻辑)

1. unlock释放锁方法

// 位于RentrantLock中:释放锁的方法
public void unlock() {
    // 释放锁
    sync.release(1);
}
// 位于AQS的静态内部类Sync中:真正释放锁的方法
// RentrantLock.unlock() -> sync.release()
public final boolean release(int arg) {
    // tryRelease尝试释放锁:
    // true: 当前线程已经完全释放锁
    // false:当前线程尚未完全释放锁
    if (tryRelease(arg)) {
        // head 什么情况下会被创建出来?
        // 当持锁线程未释放线程,且持锁期间有其他线程想要获取锁时,其他线程发现无法获取锁,
        // 且此时阻塞队列是空队列,此时后续线程会为当前持锁线程构建出一个head节点(将持锁线程封装入head)
        // 然后后续线程会追加到head节点的后面(成为head的后驱)
        Node h = head;
        // 条件1:h != null成立,说明队列中的head节点已经初始化过了,ReentrantLock在试用期间,发生过多线程竞争了~
        // 条件2:h.waitStatus != 0 成立,说明当前head后面一定插入过node节点~
        if (h != null && h.waitStatus != 0)
            // 唤醒后驱节点~
            unparkSuccessor(h);
        return true;
    }
    return false;
}

2. tryRelease尝试释放锁的方法

// 位于AQS的静态内部类Sync中:尝试释放锁方法
// true: 当前线程已经完全释放锁 | false:当前线程尚未完全释放锁
protected final boolean tryRelease(int releases) {
    // state状态变量的值相减
    int c = getState() - releases;
    // 如果条件成立:说明当前线程并未持锁 -> 直接抛异常
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    // 当前线程持有锁
    // 当前线程是否已经完全释放锁,默认初始值false
    boolean free = false;
    // c == 0条件成立时:说明当前线程已经达到完全释放锁的条件
    if (c == 0) {
        // free = true 当前线程已经完全释放锁
        free = true;
        // 更新当前持锁线程为null
        setExclusiveOwnerThread(null);
    }
    // 更新state(基于CAS)
    setState(c);
    // 返回free
    return free;
}

3. unparkSuccessor唤醒后驱节点线程的方法

// 位于AQS中:唤醒后驱节点线程的方法
private void unparkSuccessor(Node node) {
    // 获取当前node节点的waitStatus状态
    int ws = node.waitStatus;
    if (ws < 0)// -1:SIGNAL 
        // 改成0的原因:因为当前节点已经完成唤醒后驱节点线程的任务了~
        compareAndSetWaitStatus(node, ws, 0);
    // s是当前节点的第一个后驱节点
    Node s = node.next;
    // 条件1:s == null
    // s 什么时候为null?
    // 1.当前节点就是tail节点时,s==null
    // 2.当前节点入队未完成时(1.设置新节点的prev指向pred  2.CAS设置新节点为tail  3.(未完成)pred.next -> 新节点)
    // 需要找到可以被唤醒的节点...
    // 条件2:s.waitStatus > 0 前提是 s == null
    // 如果条件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最近的一个可以被唤醒的节点,该节点可能找不到,可能是null
    }
    // 如果找到合适的,且可以被唤醒的节点s,则将其挂起,如果没找到,则什么也不做~
    if (s != null)
        LockSupport.unpark(s.thread);
}

上一篇文章:AQS源码探究_03 成员方法解析(加锁、资源竞争逻辑) 和 本篇文章的前面部分,都是在介绍ReentrantLock的lock()加锁方式,这种加锁方式是不可以被响应中断的,下面我们分析可以被响应中断的加锁方式lockInterruptibly():


扩展:AQS成员方法解析(响应中断加锁逻辑)

1. lockInterruptibly可以被响应中断的加锁方法

// 位于ReentrantLock中:可以被响应中断的加锁方法
public void lockInterruptibly() throws InterruptedException {
    // 可以被响应中断的方式去竞争资源~
    sync.acquireInterruptibly(1);
}
// 位于AQS的Sync静态内部类中:竞争资源的方法(可以被响应中断)
public final void acquireInterruptibly(int arg)
        throws InterruptedException {
    // 如果当前线程已经是有中断标记interrupted为true了,则直接抛出中断异常~
    if (Thread.interrupted())
        throw new InterruptedException();
    // 尝试获取锁
    if (!tryAcquire(arg))
        doAcquireInterruptibly(arg);
}
private void doAcquireInterruptibly(int arg)
    throws InterruptedException {
    final Node node = addWaiter(Node.EXCLUSIVE);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            // 我们主要来分析下cancelAcquire这个方法: 取消指定node参与竞争。
            cancelAcquire(node);
    }

2. cancelAcquire取消指定node参与竞争的方法

// 位于AQS下
/**
 * 取消指定node参与竞争。
 */
private void cancelAcquire(Node node) {
    //空判断..
    if (node == null)
        return;
    //因为已经取消排队了..所以node内部关联的当前线程,置为Null就好了。。
    node.thread = null;
    //获取当前取消排队node的前驱。
    Node pred = node.prev;
    while (pred.waitStatus > 0)
        node.prev = pred = pred.prev;
    //拿到前驱的后继节点。
    //1.当前node
    //2.可能也是 ws > 0 的节点。
    Node predNext = pred.next;
    //将当前node状态设置为 取消状态  1
    node.waitStatus = Node.CANCELLED;
    /**
     * 当前取消排队的node所在 队列的位置不同,执行的出队策略是不一样的,一共分为三种情况:
     * 1.当前node是队尾  tail -> node
     * 2.当前node 不是 head.next 节点,也不是 tail
     * 3.当前node 是 head.next节点。
     */
    //条件一:node == tail  成立:当前node是队尾  tail -> node
    //条件二:compareAndSetTail(node, pred) 成功的话,说明修改tail完成。
    if (node == tail && compareAndSetTail(node, pred)) {
        //修改pred.next -> null. 完成node出队。
        compareAndSetNext(pred, predNext, null);
    } else {
        //保存节点 状态..
        int ws;
        //第二种情况:当前node 不是 head.next 节点,也不是 tail
        //条件一:pred != head 成立, 说明当前node 不是 head.next 节点,也不是 tail
        //条件二: ((ws = pred.waitStatus) == Node.SIGNAL || (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL)))
        //条件2.1:(ws = pred.waitStatus) == Node.SIGNAL   成立:说明node的前驱状态是 Signal 状态   不成立:前驱状态可能是
        // 极端情况下:前驱也取消排队了..
        //条件2.2:(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))
        // 假设前驱状态是 <= 0 则设置前驱状态为 Signal状态..表示要唤醒后继节点。
        //if里面做的事情,就是让pred.next -> node.next  ,所以需要保证pred节点状态为 Signal状态。
        if (pred != head &&
                ((ws = pred.waitStatus) == Node.SIGNAL ||
                        (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
                pred.thread != null) {
            //情况2:当前node 不是 head.next 节点,也不是 tail
            //出队:pred.next -> node.next 节点后,当node.next节点 被唤醒后
            //调用 shouldParkAfterFailedAcquire 会让node.next 节点越过取消状态的节点
            //完成真正出队。
            Node next = node.next;
            if (next != null && next.waitStatus <= 0)
                compareAndSetNext(pred, predNext, next);
        } else {
            //当前node 是 head.next节点。  更迷了...
            //类似情况2,后继节点唤醒后,会调用 shouldParkAfterFailedAcquire 会让node.next 节点越过取消状态的节点
            //队列的第三个节点 会 直接 与 head 建立 双重指向的关系:
            //head.next -> 第三个node  中间就是被出队的head.next 第三个node.prev -> head
            unparkSuccessor(node);
        }
        node.next = node; // help GC
    }
}

3. parkAndCheckInterruptpark当前线程方法

//AQS#parkAndCheckInterrupt
//park当前线程 将当前线程 挂起,唤醒后返回当前线程是否为中断信号唤醒。
private final boolean parkAndCheckInterrupt() {
    // 挂起当前线程
    LockSupport.park(this);
    // 返回当前线程的中断标识
    return Thread.interrupted();
}

小结

下面总结一下前面几篇文章的主要内容:


(1)AQS是Java中几乎所有锁和同步器的一个基础框架,这里说的是“几乎”,因为有极个别确实没有通过AQS来实现;


(2)AQS中维护了一个队列,这个队列使用双链表实现,用于保存等待锁排队的线程;


(3)AQS中维护了一个状态变量,控制这个状态变量就可以实现加锁解锁操作了;


(4)基于AQS自己动手写一个锁非常简单,只需要实现AQS的几个方法即可。


相关文章
|
19天前
|
算法 测试技术 C语言
深入理解HTTP/2:nghttp2库源码解析及客户端实现示例
通过解析nghttp2库的源码和实现一个简单的HTTP/2客户端示例,本文详细介绍了HTTP/2的关键特性和nghttp2的核心实现。了解这些内容可以帮助开发者更好地理解HTTP/2协议,提高Web应用的性能和用户体验。对于实际开发中的应用,可以根据需要进一步优化和扩展代码,以满足具体需求。
83 29
|
15天前
|
前端开发 数据安全/隐私保护 CDN
二次元聚合短视频解析去水印系统源码
二次元聚合短视频解析去水印系统源码
30 3
|
17天前
|
JavaScript 算法 前端开发
JS数组操作方法全景图,全网最全构建完整知识网络!js数组操作方法全集(实现筛选转换、随机排序洗牌算法、复杂数据处理统计等情景详解,附大量源码和易错点解析)
这些方法提供了对数组的全面操作,包括搜索、遍历、转换和聚合等。通过分为原地操作方法、非原地操作方法和其他方法便于您理解和记忆,并熟悉他们各自的使用方法与使用范围。详细的案例与进阶使用,方便您理解数组操作的底层原理。链式调用的几个案例,让您玩转数组操作。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
17天前
|
负载均衡 JavaScript 前端开发
分片上传技术全解析:原理、优势与应用(含简单实现源码)
分片上传通过将大文件分割成多个小的片段或块,然后并行或顺序地上传这些片段,从而提高上传效率和可靠性,特别适用于大文件的上传场景,尤其是在网络环境不佳时,分片上传能有效提高上传体验。 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
25天前
|
存储 前端开发 JavaScript
在线教育网课系统源码开发指南:功能设计与技术实现深度解析
在线教育网课系统是近年来发展迅猛的教育形式的核心载体,具备用户管理、课程管理、教学互动、学习评估等功能。本文从功能和技术两方面解析其源码开发,涵盖前端(HTML5、CSS3、JavaScript等)、后端(Java、Python等)、流媒体及云计算技术,并强调安全性、稳定性和用户体验的重要性。
|
5月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
153 2
|
27天前
|
移动开发 前端开发 JavaScript
从入门到精通:H5游戏源码开发技术全解析与未来趋势洞察
H5游戏凭借其跨平台、易传播和开发成本低的优势,近年来发展迅猛。接下来,让我们深入了解 H5 游戏源码开发的技术教程以及未来的发展趋势。
|
2月前
|
机器学习/深度学习 自然语言处理 算法
生成式 AI 大语言模型(LLMs)核心算法及源码解析:预训练篇
生成式 AI 大语言模型(LLMs)核心算法及源码解析:预训练篇
150 0
|
4月前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是"将对象的创建与使用分离”。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。创建型模式分为5种:单例模式、工厂方法模式抽象工厂式、原型模式、建造者模式。
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
4月前
|
存储 设计模式 算法
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。 行为型模式分为: • 模板方法模式 • 策略模式 • 命令模式 • 职责链模式 • 状态模式 • 观察者模式 • 中介者模式 • 迭代器模式 • 访问者模式 • 备忘录模式 • 解释器模式
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析

热门文章

最新文章

推荐镜像

更多