码农知识堂 - 1000bd
  •   Python
  •   PHP
  •   JS/TS
  •   JAVA
  •   C/C++
  •   C#
  •   GO
  •   Kotlin
  •   Swift
  • JUC锁:核心类AQS源码详解


    🚀 优质资源分享 🚀

    学习路线指引(点击解锁) 知识定位 人群定位
    🧡 Python实战微信订餐小程序 🧡 进阶级 本课程是python flask+微信小程序的完美结合,从项目搭建到腾讯云部署上线,打造一个全栈订餐系统。
    💛Python量化交易实战💛 入门级 手把手带你打造一个易扩展、更安全、效率更高的量化交易系统

    目录* 1 疑点todo和解疑

    • 2 AbstractQueuedSynchronizer学习总结
      • 2.1 AQS要点总结
      • 2.2 细节分析
        • 2.2.1 插入节点时先更新prev再更新前驱next
        • 2.2.2 为什么unparkSuccessor()要从尾部往前遍历
        • 2.2.3 AQS的设计,尽快唤醒其他等待线程体现在3个地方
    • 3 AQS 简介
      • 3.1 AQS核心思想
      • 3.2 对资源的共享方式
      • 3.3 AQS数据结构
    • 4 AbstractQueuedSynchronizer源码分析
      • 4.1 类的继承关系
        • 4.1.1 AQS需要子类重写的方法
      • 4.2 类的常量/成员变量
      • 4.3 静态内部类Node
      • 4.4 构造函数
      • 4.5 核心方法分析
        • 4.5.1 核心方法概览
        • 4.5.2 acquire()方法
        • 4.5.3 addWaiter()方法
        • 4.5.4 enq()方法
        • 4.5.5 acquireQueue()方法
        • 4.5.6 shouldParkAfterFailedAcquire()方法
        • 4.5.7 parkAndCheckInterrupt()方法
        • 4.5.8 cancelAcquire()方法
        • 4.5.9 unparkSuccessor()方法
        • 4.5.10 release()方法
        • 4.5.11 acquireSharedInterruptibly()方法
        • 4.5.12 doAcquireSharedInterruptibly()方法
        • 4.5.13 setHeadAndPropagate()方法
        • 4.5.14 doReleaseShared()方法
        • 4.5.15 releaseShared()方法
    • 5 取消节点移出链表分析
    • 6 在shared模式中为什么需要PROPAGATE状态

    1 疑点todo和解疑

    • 共享资源,这里面哪个地方体现了资源?

    同步状态变量:state就是那个共享资源(private volatile int state;) Lock类继承AQS类并定义lock()、unLock()的方法,表示获取锁和释放锁。多线程并发访问同一个lock实例,lock()方法会cas修改state变量,修改成功的线程获得锁,其他线程进入AQS队列等待。

    • sync队列的head为什么要交替,能不能共用最开始的head?

    没有必要!sync队列是双向链表结构,出队时,head交替方式,只需要修改head和head后继2个节点引用关系;固定head,就要修改head,head后继,以及head后继的后继 共3个节点。显然前者效率更高

    • node.prev = pred = pred.prev; 没弄懂这行代码

    • 为什么在cancelAquire的unParkSuccess中,能将node的后继节点unpark() 万一node前面还有signal节点呢?

    不存在的,因为经过判断得出此时node就是head的后继。并且必须由这个取消节点node来唤醒后继,要不node线程结束后,就没有线程能够唤醒队列里的其他节点了。

    • shouldParkAfterFailedAcquire有一个疑问,如果线程被unPark唤醒后,tryAcquire()失败了,那么线程会再次进入parkAndCheckInterrupt 再次park阻塞起来,那么谁来唤醒线程呢?

    先说结果:由抢到锁的那个线程来唤醒!
    上述的场景是存在的,例如在非公平锁模式中,B线程被A线程唤醒,A结束,B成为head,B去执行tryAcquire(),但此时C线程抢占到锁,B执行tryAcquire()没有拿到锁,再次park阻塞。C线程执行结束后将A唤醒

    • shouldParkAfterFailedAcquire为什么一定要先判断或者修改前置节点状态改为SIGNAL:-1,才会park阻塞?

    只有将前置节点状态改为SIGNAL,才能确保当前节点可以被前置unPark唤醒。也就是说阻塞自己前先保证一定能够被唤醒。因为代码中:
    独占模式下,唤醒后继前先限制:h.waitStatus != 0
    共享模式下,唤醒后继前先限制:h.waitStatus=SIGNAL

    • 如何理解这里说的中断:acquire()函数以独占模式获取(资源),忽略中断,即线程在aquire过程中,中断此线程是无效的

    表示本线程在获取资源期间,如果被其他线程中断,本线程不会因为中断而取消获取资源,只是将中断标记传递下去。

    • 怎么理解独占模式和共享模式
     When acquired in exclusive mode,
     * attempted acquires by other threads cannot succeed. Shared mode
     * acquires by multiple threads may (but need not) succeed. This class
     * does not "understand" these differences except in the
     * mechanical sense that when a shared mode acquire succeeds, the next
     * waiting thread (if one exists) must also determine whether it can
     * acquire as well. Threads waiting in the different modes share the
     * same FIFO queue.
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    1. 共享模式:允许多个线程同时获取资源;当一个节点的线程获取共享资源后,需要要通知后继共享节点的线程,也可以获取了。共享节点具有传播性,传播性的目的也是尽快通知其他等待的线程尽快获取锁。
    2. 独占模式: 只能够一个线程占有资源,其它尝试获取资源的线程将会进入到队列等待。
    3. 响应中断并终止:线程只要被中断就不会获取资源:两种情况的中断:1、刚尝试获取、2、进入队列中等待,前者立即停止获取,后者执行取消逻辑,等待节点变为取消状态
    • 共享模式,什么时候head状态变为PROPAGATE,这个状态值的影响是什么?

    A、B先后进入队列,状态都是0。A获得资源,进入setHeadAndPropagate晋升为head,A进入doReleaseShared尝试唤醒B时,但B还没将A改为signal,因为A还是0,A将状态改为PROPAGATE

    2 AbstractQueuedSynchronizer学习总结

    2.1 AQS要点总结

    对于AbstractQueuedSynchronizer的分析,最核心的就是sync queue的分析。

    1. 每一个节点都是由前一个节点唤醒
    2. 当节点发现前驱节点是head并且尝试获取成功,则会轮到该线程运行。
    3. condition queue中的节点向sync queue中转移是通过signal操作完成的。
    4. SIGNAL,表示后面的节点需要运行。
    5. PROPAGATE:就是为了避免线程无法会唤醒的窘境。因为共享锁会有很多线程获取到锁或者释放锁,所以有些方法是并发执行的,就会产生很多中间状态,而PROPAGATE就是为了让这些中间状态不影响程序的正常运行。

    2.2 细节分析

    2.2.1 插入节点时先更新prev再更新前驱next

    //addWaiter():
    node.prev = pred; // 1 更新node节点的prev域
    if (compareAndSetTail(pred, node)) {
        pred.next = node; //2 更新node前驱的next域
        return node;
    }
    //enq():
    node.prev = t; // 1 更新node节点的prev域
    if (compareAndSetTail(t, node)) {
        t.next = node;//2 更新node前驱的next域
        return t;
    }
    //unparkSuccessor():
    Node s = node.next; //通过.next来直接获取到节点的后继节点,这个节点的后继的prev一定指向节点本身
          //....
            if (s != null)
                LockSupport.unpark(s.thread);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    1. addWaiter() 或者enq()插入节点时,都是先更新节点的prev域,再更新它前驱的next域。那么通过node.next()取到的后继,后继的prev域一定是指向node本身。如果先更新next域,在更新prev域时出现异常,那么通过.next取到不是完整的节点
    2. unparkSuccessor()唤醒后继时,Node s = node.next; 通过.n
  • 相关阅读:
    k8s集群pod和node状态监控
    【GlobalMapper精品教程】005:影像拼接与裁剪案例教程
    热迁移中VirtIO-PCI设备的配置空间处理
    Flink 写入Apache Arrow
    本周XR新闻:吴德周成立AR硬件公司,SideQuest支持PICO、Magic Leap
    C++知识点2:把数据写进switch case结构,和写进json结构,在使用上有什么区别
    电脑入门: 路由器初学者完全教程
    爆字段名中select * from users as a join users解释
    前端培训丁鹿学堂:vue基础之v-model使用详情
    从源码看vue(v2.7.10)的computed的实现原理
  • 原文地址:https://blog.csdn.net/u013190417/article/details/126697212
  • 最新文章
  • 沪漂五周年了:我越来越迷茫了
    Agentic Skill Routing 实战:别再把所有 Skill 塞进 AI Agent 上下文
    MySQL-Seconds_behind_master的精度误差
    [MAF预定义ChatClient中间件-03]CachingChatClient——利用缓存省钱省时间
    AI的至暗历史:从万众期待到被政府撤资,AI的两次死亡徘徊
    Agent OS :五种驯服不确定性的范式
    PortSwigger SQL注入LAB11
    数据库即时编译JIT
    [Begin]AI Learn Data Day 0
    深度学习进阶(二十七)现代 LLM 的核心架构设计其二:SwiGLU
  • 热门文章
  • 十款代码表白小特效 一个比一个浪漫 赶紧收藏起来吧!!!
    奉劝各位学弟学妹们,该打造你的技术影响力了!
    五年了,我在 CSDN 的两个一百万。
    Java俄罗斯方块,老程序员花了一个周末,连接中学年代!
    面试官都震惊,你这网络基础可以啊!
    你真的会用百度吗?我不信 — 那些不为人知的搜索引擎语法
    心情不好的时候,用 Python 画棵樱花树送给自己吧
    通宵一晚做出来的一款类似CS的第一人称射击游戏Demo!原来做游戏也不是很难,连憨憨学妹都学会了!
    13 万字 C 语言从入门到精通保姆级教程2021 年版
    10行代码集2000张美女图,Python爬虫120例,再上征途
小工具 小游戏
Copyright © 2022 侵权请联系2656653265@qq.com    京ICP备2022015340号-1

京公网安备 11010502049817号