ReentrantLock 原理解析(下)

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: ReentrantLock 原理解析

node.predecessor(); 方法获取前节点,之前我们有看到 aqs 是必须要初始化的,通常情况下都不是 null 的。


final Node predecessor() throws NullPointerException {
    Node p = prev;
    if (p == null)
        throw new NullPointerException();
    else
        return p;
}


shouldParkAfterFailedAcquire()


shouldParkAfterFailedAcquire(p, node) 方法, 简单的讲就是把[node] 的有效前驱(有效是指node不是CANCELLED的)找到,并且将有效前驱的状态设置为SIGNAL,之后便返回true代表马上可以阻塞了。


private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        // 获取前驱节点的状态
        int ws = pred.waitStatus;
        // 如果是 SIGNAL 状态,即等待被占用的资源释放,直接返回 true
        // 准备继续调用 parkAndCheckInterrupt 方法
        if (ws == Node.SIGNAL)
            return true;
        // ws 大于 0 说明是 cancelled 状态
        if (ws > 0) {
            // 循环判断节点的前驱节点是否也是 cancelled 状态,忽略该节点重新链接队列
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            // 将当前节点的前驱节点设置为 SIGNAL 状态,用于后续唤醒操作
            // 程序第一次执行到这里返回为 false,还会进行外层第二次循环,最终从代码第 7 行返回
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }


parkAndCheckInterrupt(),


parkAndCheckInterrupt()阻塞当前线程, 就是当线程多次获取锁失败后,进入 AQS 阻塞队列中进入阻塞等待,等持有者释放资源,激活当前线程。


private final boolean parkAndCheckInterrupt() {
    // 线程挂起,程序不会继续向下珍惜i给你
    LockSupport.park(this);
    // 根据 park 方法 api 描述,程序下面三情况会继续向下执行
    // 1. 调用 unpark
    // 2. 被中断 (interrupt)
    // 3. 其他不合逻辑的返回才会继续向下执行
    // 因上述三种情况程序执行至此,返回当前线程的中断状态,并清空中断状态
    // 如果由于被中断,该方法会返回 true
    return Thread.interrupted();
}


unlock 方法


非公平锁方式解锁


解锁过程:


1、 unlock 方法源代码入,内部调用 release 方法并且传入一个 1 ,其实可以理解为归还/释放一个这把锁。


image.png


2、release 方法中有两个操作:第一步是执行 tyRelease 方法进行真正的解锁,如果解锁失败返回 false 返回, 第二部如果解锁成功,会去判断 AQS 中是否有等待的节点(即等待的线程)如果有就调用 unparkSuccessor 去 unpark 队列中第一个等待的线程。


image.png


3、我们先来看 tryRelease 方法, 首先是获取 state 变量的值,然后通过getState() - releases 来获取解锁后的值,再此之前还有一个判断就是判断的当前锁的持有者是否是当前线程,如果不是将抛出:IllegalMonitorStateException 异常;然后解锁后 state == 0 这个状态我们可以理解为 “完全解锁“ (其实这个是相对于锁重入来说的)。如果完全解锁就清空当前锁的线程持有者。然后在通过 cas 修改 state 的值。最终返回接锁后的布尔值。 其实这里有个小细节就是只有在 state = 0 的时候解锁的布尔值才会返回 true.


protected final boolean tryRelease(int releases) {
    // state -1
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        free = true;
        // 清空锁持有者
        setExclusiveOwnerThread(null);
    }
    // cas 修改 state
    setState(c);
    return free;
}


4、unparkSuccessor(h);方法主要是实现 AQS 中节点的唤醒操作, 它接受的一个参数就是我们在第二个步骤的时候传入的 node 节点,其实就是 AQS 的对头节点。这里通常就在头节点的下一个节点 , 如果没有找到会通过尾节点向前查找。最终确定需要唤醒的排队节点 s 执行 LockSupport.unpark(s.thread) 方法进入前面的抢锁的逻辑。


private void unparkSuccessor(Node node) {
        /*
         * If status is negative (i.e., possibly needing signal) try
         * to clear in anticipation of signalling.  It is OK if this
         * fails or if status is changed by waiting thread.
         */
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);
        /*
         * Thread to unpark is held in successor, which is normally
         * just the next node.  But if cancelled or apparently null,
         * traverse backwards from tail to find the actual
         * non-cancelled successor.
         */
        Node s = node.next;
        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;
        }
        if (s != null)
            // 排队节点唤醒
            LockSupport.unpark(s.thread);
    }


ReentrantLock 总结


流程图梳理:


image.png


其实我刚开始看这块的时候,还事比较乱的,大家可以按照自己的思路,结合 ReentrantLock 源码尝试去分析,才能逐步的清晰和掌握。


参考资料





相关文章
|
12天前
|
存储 算法 Java
解析HashSet的工作原理,揭示Set如何利用哈希算法和equals()方法确保元素唯一性,并通过示例代码展示了其“无重复”特性的具体应用
在Java中,Set接口以其独特的“无重复”特性脱颖而出。本文通过解析HashSet的工作原理,揭示Set如何利用哈希算法和equals()方法确保元素唯一性,并通过示例代码展示了其“无重复”特性的具体应用。
30 3
|
5天前
|
数据采集 存储 编解码
一份简明的 Base64 原理解析
Base64 编码器的原理,其实很简单,花一点点时间学会它,你就又消除了一个知识盲点。
26 3
|
24天前
|
前端开发 Java 应用服务中间件
21张图解析Tomcat运行原理与架构全貌
【10月更文挑战第2天】本文通过21张图详细解析了Tomcat的运行原理与架构。Tomcat作为Java Web开发中最流行的Web服务器之一,其架构设计精妙。文章首先介绍了Tomcat的基本组件:Connector(连接器)负责网络通信,Container(容器)处理业务逻辑。连接器内部包括EndPoint、Processor和Adapter等组件,分别处理通信、协议解析和请求封装。容器采用多级结构(Engine、Host、Context、Wrapper),并通过Mapper组件进行请求路由。文章还探讨了Tomcat的生命周期管理、启动与停止机制,并通过源码分析展示了请求处理流程。
|
21天前
|
开发框架 缓存 前端开发
electron-builder 解析:你了解其背后的构建原理吗?
本文首发于微信公众号“前端徐徐”,详细解析了 electron-builder 的工作原理。electron-builder 是一个专为整合前端项目与 Electron 应用的打包工具,负责管理依赖、生成配置文件及多平台构建。文章介绍了前端项目的构建流程、配置信息收集、依赖处理、asar 打包、附加资源准备、Electron 打包、代码签名、资源压缩、卸载程序生成、安装程序生成及最终安装包输出等环节。通过剖析 electron-builder 的原理,帮助开发者更好地理解和掌握跨端桌面应用的构建流程。
49 2
|
2天前
|
供应链 安全 分布式数据库
探索区块链技术:从原理到应用的全面解析
【10月更文挑战第22天】 本文旨在深入浅出地探讨区块链技术,一种近年来引起广泛关注的分布式账本技术。我们将从区块链的基本概念入手,逐步深入到其工作原理、关键技术特点以及在金融、供应链管理等多个领域的实际应用案例。通过这篇文章,读者不仅能够理解区块链技术的核心价值和潜力,还能获得关于如何评估和选择适合自己需求的区块链解决方案的实用建议。
8 0
|
13天前
|
前端开发 JavaScript UED
axios取消请求CancelToken的原理解析及用法示例
axios取消请求CancelToken的原理解析及用法示例
47 0
|
16天前
|
存储 缓存 数据处理
深度解析:Hologres分布式存储引擎设计原理及其优化策略
【10月更文挑战第9天】在大数据时代,数据的规模和复杂性不断增加,这对数据库系统提出了更高的要求。传统的单机数据库难以应对海量数据处理的需求,而分布式数据库通过水平扩展提供了更好的解决方案。阿里云推出的Hologres是一个实时交互式分析服务,它结合了OLAP(在线分析处理)与OLTP(在线事务处理)的优势,能够在大规模数据集上提供低延迟的数据查询能力。本文将深入探讨Hologres分布式存储引擎的设计原理,并介绍一些关键的优化策略。
61 0
|
22天前
|
SQL 分布式计算 大数据
大数据-97 Spark 集群 SparkSQL 原理详细解析 Broadcast Shuffle SQL解析过程(一)
大数据-97 Spark 集群 SparkSQL 原理详细解析 Broadcast Shuffle SQL解析过程(一)
33 0
|
22天前
|
SQL 分布式计算 算法
大数据-97 Spark 集群 SparkSQL 原理详细解析 Broadcast Shuffle SQL解析过程(二)
大数据-97 Spark 集群 SparkSQL 原理详细解析 Broadcast Shuffle SQL解析过程(二)
61 0
|
26天前
|
Java C语言 Python
解析Python中的全局解释器锁(GIL):影响、工作原理及解决方案
解析Python中的全局解释器锁(GIL):影响、工作原理及解决方案
31 0

推荐镜像

更多