提升--09-1--AQS底层逻辑实现

news2024/11/18 8:25:55

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 一、怎么解释AQS是什么?
    • ==AQS的本质是JUC包下一个抽象类,AbstractQueuedSynchronizer (抽象的队列式同步器)==
  • 二、AQS核心底层和Lock是什么关系?
    • ReentrantLock的互斥锁功能就是基于AQS实现的。
    • 优先聊一下lock方法的区别。
    • 分析一下acquire方法中做了什么事
  • 三、AQS如何尝试获取资源?
    • 非公平锁的tryAcquire实现
    • 公平锁的实现
  • 四、AQS获取资源失败如何排队?
  • 五、AQS排队后如何重新尝试获取资源?
  • 六、AQS如何释放资源?


一、怎么解释AQS是什么?

AQS的本质是JUC包下一个抽象类,AbstractQueuedSynchronizer (抽象的队列式同步器)

  • AQS是JUC下的一个基础类,目的是为了提供一个共性的功能,让其他类去继承,比如ReentrantLock,CountDownLatch,Semaphore,ThreadPoolExecutor…………
    在这里插入图片描述在这里插入图片描述

二、AQS核心底层和Lock是什么关系?

ReentrantLock的互斥锁功能就是基于AQS实现的。

通过源码可以看到,ReentrantLock类并没有直接继承AQS,而是ReentrantLock的内部类Sync继承了AQS。

在这里插入图片描述

Sync也是一个抽象类,他下面有两个实现。一个FairSync,一个NonfairSync。一个公平锁,一个非公平锁。

在这里插入图片描述

在这里插入图片描述
在ReentrantLock中,公平锁和非公平锁对于lock方法和tryAcquire方法的实现是不同的。

优先聊一下lock方法的区别。

// 非公平锁的lock方法
final void lock() {
    // 不管是否有线程在持有锁资源,直接尝试将state从0改为1,尝试拿锁。
    if (compareAndSetState(0, 1))
        // 拿锁成功了。将当前线程设置到exclusiveOwnerThread
        setExclusiveOwnerThread(Thread.currentThread());
    else
        // 前面抢锁失败走acquire
        acquire(1);
}

// 公平锁的lock方法
final void lock() {
    acquire(1);
}

分析一下acquire方法中做了什么事

// acquire方法实现
public final void acquire(int arg) {
    //1、tryAcquire方法:尝试获取锁资源的过程。拿到锁返回true,反之返回false。
    // 没拿到锁,才会走2和3。
    //2、addWaiter方法: 将当前没拿到锁的线程封装为Node,添加到同步队列。
    //3、acquireQueued方法: 长时间等待需要挂起线程,并且等到线程排到第一名时,
    //                      需要再次尝试获取锁资源
    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

三、AQS如何尝试获取资源?

tryAcquire方法是如何尝试获取锁资源的,tryAcquire方法有两种实现,一种是公平,一种是非公平。

  • 判断state为0,那就根据情况抢锁
  • state不为0,但是当前线程持有锁,那就走锁重入的逻辑
  • 前面都不满足,告辞!

非公平锁的tryAcquire实现

// 非公平锁的实现。
final boolean nonfairTryAcquire(int acquires) {
    // 拿到当前线程。
    final Thread current = Thread.currentThread();
    // 获取state
    int c = getState();
    // 判断state是否为0
    if (c == 0) {
        // 当前没有线程持有锁。非公平锁直接尝试抢锁,抢成功就返回true
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // 有线程持有锁。那就判断持有锁的线程是不是当前线程。
    else if (current == getExclusiveOwnerThread()) {
        // 到这说明是锁重入操作。
        // 将state + 1
        int nextc = c + acquires;
        // 判断+1之后,如果小于0,说明超过int正整数的取值范围了,无法再次重入~
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        // 将 + 1后的值赋值给state
        setState(nextc);
        // 返回true,锁重入成功~
        return true;
    }
    //没拿到锁,返回false
    return false;
}

公平锁的实现

// 公平锁的实现。
protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        // 公平锁这里多了一行hasQueuedPredecessors()
        // 如果没有线程持有锁资源,优先查看是否有排队的线程
        // 1、如果没有线程排队,返回false,代表可以抢锁。
        // 2、如果有排队的,但是当前线程排在“第一名”,返回false,代表可以抢锁。
        // 3、如果有排队的,但是当前线程没有排在“第一名”,返回true,代表不可以抢锁。
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;a
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

四、AQS获取资源失败如何排队?

如果获取锁资源失败,会执行addWaiter方法,去做排队操作。

  1. 将当前线程封装Node。
  2. 如果同步队列为null,需要优先初始化一个虚拟的Node节点
  3. 将当前Node添加到tail的后面。
private Node addWaiter(Node mode) {
    // 将当前线程封装Node
    Node node = new Node(Thread.currentThread(), mode);
    // 拿到尾结点
    Node pred = tail;
    // 不为null,代表现在有Node对象。
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    // 如果pred是null,
    enq(node);
    return node;
}
// 将node添加到同步队列
private Node enq(final Node node) {
    // 死循环是为了确保一定能添加成功
    for (;;) {
        Node t = tail;
        if (t == null) { // Must initialize
            // 初始化一个没有线程信息的Node,作为头尾
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

五、AQS排队后如何重新尝试获取资源?

  • 挂起需要确保prev节点的状态为-1。
  • 重新获取锁资源需要确保当前node是head.next,并且基于tryAcquire去获取锁资源。
// 排队后的挂起操作和获取锁资源的操作
// node就是刚刚去排队的Node
final boolean acquireQueued(final Node node, int arg) {
    // 拿锁失败了么??true
    boolean failed = true;
    try {
        // 死循环,拿到锁才能走!!
        for (;;) {
            // 拿到当前Node的上一个Node
            final Node p = node.prev;
            // 只有head.next的node才有资格抢锁
            if (p == head && tryAcquire(arg)) {
                // 说明拿到锁资源了。
                // 当前node成为新的head,线程和prev都设置为null
                setHead(node);
                p.next = null; 
                failed = false;
                // 拿到成功,返回中断标记位(这里省略了这部分代码)
                return false;
            }
            // 没资格拿,或者没拿到!
            // 需要优先掌握一个知识,Node中有一个waitStatus的状态
            // 1:代表当前Node取消了,不排了,告辞,走人。
            // 0:代表默认状态,啥事没有~
            // -1:代表当前Node的next节点可能挂起了。
            if (shouldParkAfterFailedAcquire(p, node) &&
                // 基于Unsafe类的park方法,将当前线程挂起!
                parkAndCheckInterrupt())
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    // pred是prev
    // node是curr
    // 拿到上一个Node的状态
    int ws = pred.waitStatus;
    // 如果上一个Node状态是-1,返回true,代表可以挂起
    if (ws == Node.SIGNAL)
        return true;

    // 上一个节点状态是否是取消状态
    if (ws == 1) {
        // 绕过状态为1的节点,找到一个状态正常的。
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus == 1);
        pred.next = node;
    } else {
        // 如果上一个节点状态正常,直接修改为-1
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

六、AQS如何释放资源?

  • 加锁是执行lock,释放锁资源玩的是unlock方法。
  • 释放锁会对state做-1操作,如果-1后state为0,代表释放锁资源成功。
  • 如果锁资源释放成功,需要查看head状态是否为-1,如果为-1需要唤醒离head最近的有效节点。
// 释放锁资源
public final boolean release(int arg) {
    // 执行tryAcquire释放锁资源
    if (tryRelease(arg)) {
        // 释放干净了!
        // 先拿head
        Node h = head;
        // 如果head不为null,head的状态是否为 -1
        if (h != null && h.waitStatus == -1)
            // 唤醒后面挂起的线程(睡觉的线程)
            unparkSuccessor(h);
        return true;
    }
    return false;
}
// 释放锁操作。
protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    // 释放锁资源的线程必须是持有锁资源的线程,否则甩你一个异常。
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    // free代表锁释放干净了么?
    boolean free = false;
    // 如果state为0,代表锁资源释放干净了
    // 没进if,就代表没释放干净
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }

    setState(c);
    return free;
}

// 唤醒后续挂起的线程   node是head
private void unparkSuccessor(Node node) {
    // 拿到head的状态
    int ws = node.waitStatus;
    // 状态是-1,归位0
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
    // s可能就是要被唤醒的线程
    Node s = node.next;
    // 如果s节点出现了问题,不排队了,那就找离head最近的有效节点唤醒!
    if (s == null || s.waitStatus > 0) {
        s = null;
        // 会从tail开始往前找,找到离head最近的有效节点
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    // 如果s不为null,代表找到了具体要唤醒的Node
    if (s != null)
        // 唤醒对应的线程
        LockSupport.upnark(s.thread);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1234999.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于霍克斯过程的限价订单簿模型下的深度强化学习做市策略

数量技术宅团队在CSDN学院推出了量化投资系列课程 欢迎有兴趣系统学习量化投资的同学&#xff0c;点击下方链接报名&#xff1a; 量化投资速成营&#xff08;入门课程&#xff09; Python股票量化投资 Python期货量化投资 Python数字货币量化投资 C语言CTP期货交易系统开…

【secureCRT连接Virtual Box里安装的Utuntu】

先说一下为什么要写这篇文章及一些背景问题介绍&#xff0c;楼主第一次使用secureCRT及securtFX这两个软件&#xff0c;在windows系统下访问虚拟机里面的ubuntu系统。看了网上的不少帖子&#xff0c;没有让我清晰明白地知道怎么使用secureCRT。连接不通&#xff0c;不知道是虚拟…

【Python】给出一个包含n个整数的数列,问整数a在数列中的第一次出现是第几个。

问题描述 给出一个包含n个整数的数列&#xff0c;问整数a在数列中的第一次出现是第几个。 输入格式 第一行包含一个整数n。 第二行包含n个非负整数&#xff0c;为给定的数列&#xff0c;数列中的每个数都不大于10000。 第三行包含一个整数a&#xff0c;为待查找的数。 输出格式…

LangChain的函数,工具和代理(一):OpenAI的函数调用

一、什么是函数调用功能 几个月前OpenAI官方发布了其API的函数调用功能(Function calling), 在 API 调用中&#xff0c;您可以描述函数&#xff0c;并让模型智能地选择输出包含调用一个或多个函数的参数的 JSON 对象。API函数“ChatCompletion” 虽然不会实际调用该函数&#…

redis之数据类型

&#xff08;一&#xff09;关系型数据库和非关系型数据库的区别 1、关系型数据库是一个机构化的数据库&#xff0c;列和行 &#xff08;1&#xff09;列&#xff1a;声明对象 &#xff08;2&#xff09;行&#xff1a;记录对象的属性 &#xff08;3&#xff09;表与表之间…

Python (十三) 输出

程序员的公众号&#xff1a;源1024&#xff0c;获取更多资料&#xff0c;无加密无套路&#xff01; 最近整理了一波电子书籍资料&#xff0c;包含《Effective Java中文版 第2版》《深入JAVA虚拟机》&#xff0c;《重构改善既有代码设计》&#xff0c;《MySQL高性能-第3版》&…

使用pytorch利用神经网络原理进行图片的训练(持续学习中....)

1.做这件事的目的 语言只是工具,使用python训练图片数据,最终会得到.pth的训练文件,java有使用这个文件进行图片识别的工具,顺便整合,我觉得Neo4J正确率太低了,草莓都能识别成为苹果,而且速度慢,不能持续识别视频帧 2.什么是神经网络?(其实就是数学的排列组合最终得到统计结果…

Vue项目 配置项设置

一、项目运行时浏览器自动打开 找到package.json文件 找到"sctipts"配置项 在"serve"配置项最后加上--open "scripts": {"serve": "vue-cli-service serve --open","build": "vue-cli-service build&quo…

2023年【四川省安全员A证】复审考试及四川省安全员A证考试试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 四川省安全员A证复审考试根据新四川省安全员A证考试大纲要求&#xff0c;安全生产模拟考试一点通将四川省安全员A证模拟考试试题进行汇编&#xff0c;组成一套四川省安全员A证全真模拟考试试题&#xff0c;学员可通过…

Nacos介绍与使用

Nacos介绍与使用 文章目录 Nacos介绍与使用一. 什么是Nacos1 Nacos功能1.1 配置中心1.2 注册中心 2.为什么要使用Nacos 二.Nacos 部署安装1. Nacos 部署方式2. Nacos 安装3. 配置数据源4. 开启控制台授权登录&#xff08;可选&#xff09; 三. Nacos配置中心的使用1. 创建配置信…

2023/11/21JAVAweb学习

优先级高低id > 类 > 元素 格式化ctrl alt L

LeetCode热题100——动态规划

动态规划 1. 爬楼梯2. 杨辉三角3. 打家劫舍 1. 爬楼梯 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢&#xff1f; // 题解&#xff1a;每次都有两种选择&#xff0c;1或者2 int climbStairs(int n) {if (n …

GAMES101—Lec 05~06:光栅化

目录 概念回顾&#xff08;个人理解&#xff09;光栅化1.采样2.采样出现的问题&#xff1a;走样 反走样 概念回顾&#xff08;个人理解&#xff09; 屏幕&#xff1a;在图形学中&#xff0c;我们认为屏幕是一个二维数组&#xff0c;数组里的每一个元素为一个二维像素。 光栅化…

【C++进阶之路】第四篇:set和map

文章目录 一、关联式容器健值对二、set & multiset三、map & multimap在这里插入图片描述 四、set和map底层原理 一、关联式容器健值对 关联式容器 & 键值对 二、set & multiset set & multiset 三、map & multimap map & multimap 四、set和…

【AT模式连接ONENET】ONENET可视化平台的使用

02 ONENET可视化平台的使用 ATCWMODE1 设置模式 ATCWDHCP1,1 启动DHCP功能 ①ATCWJAP"ssid","password" ATCWJAP“123456789”&#xff0c;“wang020118” ②ATMQTTUSERCFG0,1,"设备名字","设备ID","你的鉴权信息""…

JAVA项目测试----用户管理系统

一)项目简介: 用户管理系统是依据于前后端分离来实现的&#xff0c;是基于Spring SpringBoot Spring MVC&#xff0c;SpringAOP&#xff0c;MyBatis等框架来实现的一个用户管理网站&#xff0c;并且已经部署到了云服务器上, 目前的用户管理系统实现了超级管理员的注册功能&…

模电 01

一.半导体基本知识 1.优点&#xff1a;体积小、重量轻、使用寿命长、输入功率小、功率转换效率高。 2.性能介于导体与绝缘体 3.常用半导体材料&#xff1a;硅&#xff08;SI&#xff09; 镉&#xff08;Ge&#xff09;,化合物半导体&#xff1a;砷化镓&#xff08;GaAs&…

【封装UI组件库系列】全局样式的定义与重置

封装UI组件库系列第二篇样式​​​​​​​ ​​​​​​&#x1f31f;前言 &#x1f31f;定义全局样式 生成主题色和不同亮度的颜色 ​编辑 中性色及其他变量 &#x1f31f;样式重置 &#x1f31f;总结 ​​​​​​​​​​​​​​&#x1f31f;前言 在前端开发中&…

SpringBoot趣探究--1.logo是如何打印出来的

一.前言 从本篇开始&#xff0c;我将对springboot框架做一个有趣的探究&#xff0c;探究一下它的流程&#xff0c;虽然源码看不懂&#xff0c;不过我们可以一点一点慢慢深挖&#xff0c;好了&#xff0c;下面我们来看一下本篇的知识&#xff0c;这个logo是如何打印出来的&#…