ReentrantLock 原理解析(中)

简介: ReentrantLock 原理解析

非公平锁 lock 方法实现


image.png


特别说明一下:下面会涉及到非公平/公平锁的解析,在解析之前我会加粗说是那种方式的锁。


acquire()


acquire 方法在 AbstractQueuedSynchronizer 类中,该方法就是去尝试获取锁,如果获取失败了,就会尝试再次获取,或者进入 AQS 队列中。


public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}


主要有三条流程(本质就是 3 个核心方法) tryAcquireaddWaiteracquireQueued


FairSync#tryAcquire


1、公平锁tryAcquire 是由子类 FairSync 实现, 这里和非公平锁的区别就在于调用了 hasQueuedPredecessors 方法判断是否有线程在排队。


protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (
            // hasQueuedPredecessors 这里就是公平锁的体现,如果人在排队就不能获取锁,需要先进入排队,所以性能上来说比非公平锁要一些。
            !hasQueuedPredecessors() &&
            // cas 获取锁
            compareAndSetState(0, acquires)) {
            // 设置锁持有者
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // 重入
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}


2、addWaiter 操作,顾名思义就是当前线程尝试获取锁失败,然后进入将当前 Thread 封装为一个 AQS Node 节点,进入等待队列排队


private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            // 后续通过这里返回
            return node;
        }
    }
    // 首次进入
    enq(node);
    return node;
}


如果没有尾节点会调用 enq进行初始化头节点和入队操作:


private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        // 首次初始化
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            // 加入当前节点
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}


3、调用acquireQueued 方法是在,当前获取锁的线程进入 AQS 之后进入阻塞之前执行的方法,其实就是在进入等待之前做最后一次 “抢救” 尝试能否获取锁。还有一个逻辑就是在 AQS 唤醒过后,当前获取到锁的节点就成了头节点,会将 "旧" 头节点丢弃掉。


final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}


NonfairSync#tryAcquire()


1、 本次走非公平锁 NonfairSync, 非公平锁对比公平锁没有是否有队列的排队的判断,只要是锁获取的参与者,都可以直接进行锁的竞争。


// 首先我们先看 `tryAcquire()` 方法
protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}
// 核心是调用 `nonfairTryAcquire(acquires)` 
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    // 如果可以获取锁
    if (c == 0) {
        // 修改 state 
        if (compareAndSetState(0, acquires)) {
            // 设置锁的持有者(持有线程)
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // 重入支持
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}


2、 addWaiter(Node.EXCLUSIVE)addWaiter方法实现


image.png


如果前面没有排队线程将调用 enq方法


image.png


双向链表中,第一个节点为虚节点(也叫做哨兵节点),其实并不存储任何信息,只是占位,真正的第一个有数据的节点,是从第二个节点开始的。


acquireQueued(addWaiter(Node.EXCLUSIVE))


final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        // 循环
        for (;;) { 
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                // 哨兵节点出队
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            // shouldParkAfterFailedAcquire 第 1 次返回 false
            // 第 2 次返回 false
            if (shouldParkAfterFailedAcquire(p, node) &&
                // 内部会阻塞线程调用 LockSupport.park(this);
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
// setHead 方法
// 就是把当前获取锁的节点设置为哨兵节点,然后之前旧哨兵节点设置为 null
private void setHead(Node node) {
    head = node;
    node.thread = null;
    node.prev = null;
}


相关文章
|
7月前
|
安全 算法 网络协议
解析:HTTPS通过SSL/TLS证书加密的原理与逻辑
HTTPS通过SSL/TLS证书加密,结合对称与非对称加密及数字证书验证实现安全通信。首先,服务器发送含公钥的数字证书,客户端验证其合法性后生成随机数并用公钥加密发送给服务器,双方据此生成相同的对称密钥。后续通信使用对称加密确保高效性和安全性。同时,数字证书验证服务器身份,防止中间人攻击;哈希算法和数字签名确保数据完整性,防止篡改。整个流程保障了身份认证、数据加密和完整性保护。
|
6月前
|
机器学习/深度学习 数据可视化 PyTorch
深入解析图神经网络注意力机制:数学原理与可视化实现
本文深入解析了图神经网络(GNNs)中自注意力机制的内部运作原理,通过可视化和数学推导揭示其工作机制。文章采用“位置-转移图”概念框架,并使用NumPy实现代码示例,逐步拆解自注意力层的计算过程。文中详细展示了从节点特征矩阵、邻接矩阵到生成注意力权重的具体步骤,并通过四个类(GAL1至GAL4)模拟了整个计算流程。最终,结合实际PyTorch Geometric库中的代码,对比分析了核心逻辑,为理解GNN自注意力机制提供了清晰的学习路径。
441 7
深入解析图神经网络注意力机制:数学原理与可视化实现
|
6月前
|
机器学习/深度学习 缓存 自然语言处理
深入解析Tiktokenizer:大语言模型中核心分词技术的原理与架构
Tiktokenizer 是一款现代分词工具,旨在高效、智能地将文本转换为机器可处理的离散单元(token)。它不仅超越了传统的空格分割和正则表达式匹配方法,还结合了上下文感知能力,适应复杂语言结构。Tiktokenizer 的核心特性包括自适应 token 分割、高效编码能力和出色的可扩展性,使其适用于从聊天机器人到大规模文本分析等多种应用场景。通过模块化设计,Tiktokenizer 确保了代码的可重用性和维护性,并在分词精度、处理效率和灵活性方面表现出色。此外,它支持多语言处理、表情符号识别和领域特定文本处理,能够应对各种复杂的文本输入需求。
729 6
深入解析Tiktokenizer:大语言模型中核心分词技术的原理与架构
|
7月前
|
机器学习/深度学习 算法 数据挖掘
解析静态代理IP改善游戏体验的原理
静态代理IP通过提高网络稳定性和降低延迟,优化游戏体验。具体表现在加快游戏网络速度、实时玩家数据分析、优化游戏设计、简化更新流程、维护网络稳定性、提高连接可靠性、支持地区特性及提升访问速度等方面,确保更流畅、高效的游戏体验。
174 22
解析静态代理IP改善游戏体验的原理
|
7月前
|
编解码 缓存 Prometheus
「ximagine」业余爱好者的非专业显示器测试流程规范,同时也是本账号输出内容的数据来源!如何测试显示器?荒岛整理总结出多种测试方法和注意事项,以及粗浅的原理解析!
本期内容为「ximagine」频道《显示器测试流程》的规范及标准,我们主要使用Calman、DisplayCAL、i1Profiler等软件及CA410、Spyder X、i1Pro 2等设备,是我们目前制作内容数据的重要来源,我们深知所做的仍是比较表面的活儿,和工程师、科研人员相比有着不小的差距,测试并不复杂,但是相当繁琐,收集整理测试无不花费大量时间精力,内容不完善或者有错误的地方,希望大佬指出我们好改进!
424 16
「ximagine」业余爱好者的非专业显示器测试流程规范,同时也是本账号输出内容的数据来源!如何测试显示器?荒岛整理总结出多种测试方法和注意事项,以及粗浅的原理解析!
|
6月前
|
传感器 人工智能 监控
反向寻车系统怎么做?基本原理与系统组成解析
本文通过反向寻车系统的核心组成部分与技术分析,阐述反向寻车系统的工作原理,适用于适用于商场停车场、医院停车场及火车站停车场等。如需获取智慧停车场反向寻车技术方案前往文章最下方获取,如有项目合作及技术交流欢迎私信作者。
388 2
|
8月前
|
机器学习/深度学习 自然语言处理 搜索推荐
自注意力机制全解析:从原理到计算细节,一文尽览!
自注意力机制(Self-Attention)最早可追溯至20世纪70年代的神经网络研究,但直到2017年Google Brain团队提出Transformer架构后才广泛应用于深度学习。它通过计算序列内部元素间的相关性,捕捉复杂依赖关系,并支持并行化训练,显著提升了处理长文本和序列数据的能力。相比传统的RNN、LSTM和GRU,自注意力机制在自然语言处理(NLP)、计算机视觉、语音识别及推荐系统等领域展现出卓越性能。其核心步骤包括生成查询(Q)、键(K)和值(V)向量,计算缩放点积注意力得分,应用Softmax归一化,以及加权求和生成输出。自注意力机制提高了模型的表达能力,带来了更精准的服务。
|
7月前
|
Java 数据库 开发者
详细介绍SpringBoot启动流程及配置类解析原理
通过对 Spring Boot 启动流程及配置类解析原理的深入分析,我们可以看到 Spring Boot 在启动时的灵活性和可扩展性。理解这些机制不仅有助于开发者更好地使用 Spring Boot 进行应用开发,还能够在面对问题时,迅速定位和解决问题。希望本文能为您在 Spring Boot 开发过程中提供有效的指导和帮助。
541 12
|
7月前
|
开发框架 监控 JavaScript
解锁鸿蒙装饰器:应用、原理与优势全解析
ArkTS提供了多维度的状态管理机制。在UI开发框架中,与UI相关联的数据可以在组件内使用,也可以在不同组件层级间传递,比如父子组件之间、爷孙组件之间,还可以在应用全局范围内传递或跨设备传递。
148 2
|
6月前
|
负载均衡 JavaScript 前端开发
分片上传技术全解析:原理、优势与应用(含简单实现源码)
分片上传通过将大文件分割成多个小的片段或块,然后并行或顺序地上传这些片段,从而提高上传效率和可靠性,特别适用于大文件的上传场景,尤其是在网络环境不佳时,分片上传能有效提高上传体验。 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~

推荐镜像

更多
  • DNS