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

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

文章目录

  • 一、怎么解释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);
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Flowable工作流高级篇

文章目录 一、任务分配和流程变量1.任务分配1.1 固定分配1.2 表达式分配1.2.1 值表达式1.2.2 方法表达式 1.3 监听器分配 2.流程变量2.1 全局变量2.2 局部变量2.3 案例讲解 二、候选人和候选人组1.候选人1.1 定义流程图1.2 部署和启动流程实例1.3 任务的查询1.4 任务的拾取1.5 …

STM32:时钟树原理概要

在一般情况下只要在CubeIDE中将RCC下的高速时钟源设置成晶振&#xff0c;随后在时钟配置中把HCLK设置到最大频率&#xff08;比如STM32F103的最高频率是72MHZ &#xff09;&#xff0c;CubeIDE就会帮我们自动调节其它参数到合适的值。这样我们芯片就可以全速运行了。 一、时钟信…

【20年扬大真题】设顺序表va中的数据元素递增有序。试写一算法,将x插入到顺序表的适当位置上,以保障该表的有序性。

【20年扬大真题】 设顺序表va中的数据元素递增有序。 试写一算法&#xff0c;将x插入到顺序表的适当位置上&#xff0c;以保障该表的有序性。 #define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<malloc.h> #define MaxSize 9//定义最大长度 int InitAr…

俄罗斯网络间谍组织在有针对性的攻击中部署LitterDrifter USB蠕虫

导语 俄罗斯网络间谍组织最近在针对乌克兰实体的攻击中&#xff0c;部署了一种名为LitterDrifter的USB蠕虫。这种蠕虫具有自动传播恶意软件的功能&#xff0c;并与威胁行为者的命令和控制服务器进行通信。该组织被称为Gamaredon&#xff0c;其攻击行动被认为是大规模的&#xf…

探寻欧洲市场的机遇:深度剖析欧洲跨境电商

随着全球化的不断推进&#xff0c;欧洲作为一个经济发达、多元文化共存的大陆&#xff0c;成为跨境电商发展的重要目标。本文将深入剖析欧洲跨境电商的机遇&#xff0c;分析欧洲市场的特点、挑战与前景&#xff0c;为企业提供在这个充满潜力的市场中蓬勃发展的指导。 欧洲市场的…

ArcGIS教程——ArcGIS工具-按线分割面

功能说明 在ArcGIS数据处理过程中&#xff0c;有时需要沿线把面要素分割开&#xff0c;可以使用高级编辑中的分割面&#xff08;Cut Polygon&#xff09;工具。那么&#xff0c;如果要用线图层分割面图层该怎么办呢&#xff1f;地理遥感生态网平台开发了一个自定义模型工具。它…

【cpolar】TortoiseSVN如何安装并实现公网提交文件到本地SVN服务器

&#x1f3a5; 个人主页&#xff1a;深鱼~ &#x1f525;收录专栏&#xff1a;cpolar &#x1f304;欢迎 &#x1f44d;点赞✍评论⭐收藏 文章目录 前言1. TortoiseSVN 客户端下载安装2. 创建检出文件夹3. 创建与提交文件4. 公网访问测试 前言 TortoiseSVN是一个开源的版本控…

MySQL InnoDB 引擎底层解析(三)

6.3.3. InnoDB 的内存结构总结 InnoDB 的内存结构和磁盘存储结构图总结如下&#xff1a; 其中的 Insert/Change Buffer 主要是用于对二级索引的写入优化&#xff0c;Undo 空间则是 undo 日志一般放在系统表空间&#xff0c;但是通过参数配置后&#xff0c;也可以用独立表空 间…

初识linux(1)

文章目录 什么是linux什么是操作系统&#xff1f;开源 怎么装linux的环境基础指令lspwdcdtouchmkdirrmdir与rmmancpmv 什么是linux linux是一款开源操作系统 什么是操作系统&#xff1f; 操作系统&#xff1a;一种对计算机所有计算机软硬件进行控制和管理的系统软件 开源 开源&…

2023年中国AI基础设施行业发展趋势分析:AI基础设施将保持高速增长[图]

从产品形态来看&#xff0c;AI基础设施可划分为AI基础硬件和基础软件两大类。而在AI生态系统中&#xff0c;通用型和定制型AI基础设施的相互依赖性促进了广泛的AI技术应用&#xff0c;也为各行业的持续发展提供了关键支持。 AI基础设施分类 资料来源&#xff1a;共研产业咨询&…

一文详解!SRM(供应商管理)助力实现采购端实现降本增效

供应商管理关系到企业各部门的正常运转&#xff0c;一个好的SRM供应商管理系统对于公司来说无疑是锦上添花&#xff0c;改善企业与供应商的关系&#xff0c;可以帮助企业实现采购端的降本增效。但在信息化转型的浪潮下&#xff0c;很多企业SRM信息化却遇到不少问题。 那么请花…

云计算:开辟数字时代的无限可能

云计算是一项革命性的技术&#xff0c;为企业和个人提供了灵活、可扩展和高效的计算资源。本文将介绍云计算的概念、架构和优势&#xff0c;并探讨其在数字化时代的重要性和未来发展趋势。 引言 随着信息技术的日新月异和数字化转型的浪潮席卷全球&#xff0c;云计算作为一种颠…

分布式系统的认证授权

一.分布式系统的认证授权大致架构 以云音乐系统为例&#xff1a; 注&#xff1a;一般情况下&#xff0c;我们会把认证的部分的接口提取为一个单独的认证服务模块中。 二.单点登录&#xff08;Single Sign On&#xff09; 单点登录&#xff0c;Single Sign On&#xff0c;简称…

OpenGL_Learn15(投光物)

1. 平行光 cube.vs******************#version 330 core layout (location 0) in vec3 aPos; layout (location 1 ) in vec3 aNormal; layout (location2) in vec2 aTexCoords;out vec3 FragPos; out vec3 Normal; out vec2 TexCoords;uniform mat4 model; uniform mat4 view…

初识树(c语言)

树 定义&#xff1a;树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合。 有一个特殊的结点&#xff0c;称为根结点&#xff0c;根节点没有前驱结点 除根节点外&#xff0c;其余结点被分成M(M>0)个互不相交…

如何在3dMax中使用Python返回场景内所有对象的列表?

如何在3dMax中使用Python返回场景内所有对象的列表&#xff1f; 3dMax支持开发基于Python的工具和扩展&#xff0c;因此可以对其进行自定义并将其集成到现代数字内容创建管道中。为此&#xff0c;3dMax集成了Python 3.9解释器&#xff0c;并通过pymxs API公开了3dMax的丰富功能…

U盘系统制作

一、简介 目标&#xff1a;将Linux和Windows系统装进U盘&#xff0c;linux称为LTG、Windows称为WTG 环境&#xff1a; 1、使用Rufus工具进行操作 2、基于windows系统进行Rufus软件进行制作 3、使用联想Y7000作为测试U盘系统启动测试机器&#xff08;无系统盘&#xff09; 优点…

基于社交网络算法优化概率神经网络PNN的分类预测 - 附代码

基于社交网络算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于社交网络算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于社交网络优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神…

Latex中文论文模板A4双栏,适用课程论文

文章目录 说明实现效果1.引入库2.摘要3.参考文献4.中文伪代码 模板下载 说明 在写课程论文的时候用了latex&#xff0c;将模板整理在这里&#xff0c;里面还有一些没有完善的地方&#xff0c;如图注、表格等。 该模板的主要使用点是&#xff0c;包含了摘要、正文双栏格式、中…