深入理解AQS

news2024/11/29 0:56:39
  1. 概念

设计初衷:该类利用 状态+队列 实现了一个同步器,更多的是提供一些模板方法(子类必须重写,不然会抛错)。
设计功能:独占、共享模式
  1. 两个核心,state、Queue

2.1 state

setState、compareAndSetState都是用于修改同步状态。看类名其实就知道一个是线程不安全的(setState),一个是使用了乐观锁来保证线程安全(compareAndSetState)。

  • 使用场景

  • setState:应用于释放资源的线程,因为同一时间只有一个使用这个线程不安全的方法去修改state的值,所以不会发生并发安全问题

  • compareAndSetState:应用于尝试获取同步器的资源,由于同一时间可能存在多个资源竞争锁,所以需要使用unsfte类的cas保证线程安全

private volatile int state; // 同步状态值,0:空闲,>0:有多少个线程在同步队列中等待
protected final int getState() { // 获取同步状态
    return state;
}
protected final void setState(int newState) { // 修改同步状态
    state = newState;
}
protected final boolean compareAndSetState(int expect, int update) {
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update); // 通过unsafe类的cas修改同步状态
}

2.2 Queue

底层:带头、尾节点的双向链表
private transient volatile Node head; // 头节点
private transient volatile Node tail; // 尾节点
static final class Node {
    volatile Node prev; // 前一个节点
    volatile Node next; // 后一个节点
}
  1. 核心获取同步锁流程

3.1 acquire

public final void acquire(int arg) {
    // 尝试获取资源失败,且成功加入同步队列,则阻塞线程
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) 
        selfInterrupt(); // 获取不到资源或者加入队列失败,那就中断该线程
}

3.2 tryAcquire

protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException(); // 模板方法,让子类实现
}

3.3 addWaiter

private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode); // 转化为同步节点(队列节点类型)
    // 尾插法
    Node pred = tail; // 指向尾节点
    if (pred != null) {
        node.prev = pred; // 新节点的前一个节点指向尾节点
        if (compareAndSetTail(pred, node)) { // 因为同时间内有多个线程进入队列,所以使用cas置换尾节点
            pred.next = node; // 原尾节点的下一个指针指向新插入的节点
            return node;
        }
    }
    enq(node); // 队列为空,需要初始化队列插入
    return node;
}

3.4 enq

private Node enq(final Node node) {
    for (;;) { // 自旋,创建到成功为止
        Node t = tail;
        if (t == null) { // 还是并发安全问题,保守判断一下,是不是有人抢先一步
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t; // 插入节点指向尾巴
            if (compareAndSetTail(t, node)) { // 交换尾节点
                t.next = node; // 上一个节点指向当前插入节点
                return t; 
            }
        }
    }
}

3.5 acquireQueued

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; // GC掉,因为当前节点获取到资源,说明上一个节点已经执行完毕业务了
                failed = false; // 设置成功
                return interrupted; // 不阻塞
            }
            if (shouldParkAfterFailedAcquire(p, node) && // 没有获取到资源,把线程挂起,别浪费资源
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node); // 获取到资源,取消尝试获取资源
    }
}

3.6 shouldParkAfterFailedAcquire

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus; // 上一个节点的wait状态
    if (ws == Node.SIGNAL) // 上一个节点是SIGNAL状态,说明可以阻塞,回去等通知就行
        return true;
    if (ws > 0) { // 如果大于0,说明是CANCELLED状态,那就把前面那些废物节点扔掉
        do {
            node.prev = pred = pred.prev;  
        } while (pred.waitStatus > 0); // 扔啊扔,扔到前一个节点不是废物节点
        pred.next = node;
    } else {
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL); // 小于0,说明前面有节点,更改成SIGNAL状态
    }
    return false;
}

3.6.1 waitStatus

static final int CANCELLED =  1; // 废弃状态,有些加入了又不想等,就玩儿
static final int SIGNAL    = -1; // 等待激活下一个节点的状态,下一个节点肯定是被Condiction.await()
static final int CONDITION = -2; // 条件状态,中间节点
static final int PROPAGATE = -3; // 共享模式下的节点状态

3.7 parkAndCheckInterrupt

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this); // 挂起当前线程
    return Thread.interrupted();
}

3.7.1 interrupted

public static boolean interrupted() {
    return currentThread().isInterrupted(true); // 获取当前线程是否为中断状态
}
private native boolean isInterrupted(boolean ClearInterrupted); // 清除中断标志
  1. 获取同步锁流程图

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

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

相关文章

SpringMVC简单仿写

之前我分享过SpringMVC的基本原理与配置(原文链接:https://blog.csdn.net/L170311/article/details/129339120),为了更深层次的学习,精益求精,手动仿写了一个MVC原理实现demo,一起学习一下吧 结构目录&…

使用预训练模型自动续写文本的四种方法

作者:皮皮雷 来源:投稿 编辑:学姐 这篇文章以中文通用领域文本生成为例,介绍四种常用的模型调用方法。在中文文本生成领域,huggingface上主要有以下比较热门的pytorch-based预训练模型: 本文用到了其中的ue…

RFID在技术在工业产线上的应用

RFID在技术在工业产线上的应用一工业产线需求制造业生产线几乎每月都要损耗大量物料,并且生产结果与预期因为有误差而影响交货的情况时有发生,生产线也往往因人为原因造成种种误差。将RFID标签贴在生产物料或产品上,可自动记录产品的数量、规…

学完Java只能在互联网公司任职吗?

当然不是只有互联网公司需要软件,需要开发技术人员,传统行业、新经济领域都有软件项目需求;Java也不是只能做网站、企业应用,还可以用于嵌入式、游戏…… 互联网时代的手机、智能电视、家具、机械设备等各种有形产品都将会嵌入智…

二、Neo4j源码研究系列 - 单步调试

二、Neo4j源码研究系列 - 单步调试 一、背景介绍 上一篇我们已经把了neo4j的源码准备以及打包流程完成了,本篇将讲解如何对neo4j进行单步调试。对于不了解如何编译打包neo4j的读者,请阅读《一、Neo4j源码研究系列 - 源代码准备》。 大纲: …

【改机教程】iOS系统去除小黑条,改拍照声、拨号音、键盘音,不用越狱,支持所有机型

大家好,上次给大家分享了几个iOS系统免越狱改机教程 今天带来最新的教程,这次修改利用的是同一个漏洞,由外网大神 tamago 开发,国内大神冷风 进行汉化和优化 可以修改的地方包括 去除底部小黑条 dock栏透明 桌面文件夹透明 桌面…

golang 占位符还傻傻分不清?

xdm ,写 C/C 语言的时候有格式控制符,例如 %s , %d , %c , %p 等等 在写 golang 的时候,也是有对应的格式控制符,也叫做占位符,写这个占位符,需要有对应的数据与之对应,不能瞎搞 基本常见常用…

Cobalt Strike---(2)

数据管理 Cobalt Strike 的团队服务器是行动期间Cobalt Strike 收集的所有信息的中间商。Cobalt Strike 解析来 自它的 Beacon payload 的输出,提取出目标、服务和凭据。 如果你想导出 Cobalt Strike 的数据,通过 Reporting → Export Data 。Cobalt Str…

CentOS7自签SSL证书并配置nginx

一、生成SSL证书 1、安装依赖包 yum install -y openssl openssl-devel 2、生成私钥,会让你输入一个 4~2048 位的密码,你需要暂时记住这个密码 openssl genrsa -des3 -out server.key 2048 输入两遍相同的密码 3、生成CSR(Certificate Signing Request …

Postgresql-12.5 visual studio-2022 windows 添加pg工程并调试

pg内核学习,记录一下 文章目录安装包编译安装VS添加Postgresql工程调试源码安装包 (1)perl下载 https://www.perl.org/get.html (2)diff下载 http://gnuwin32.sourceforge.net/packages/diffutils.htm (…

23届非科班选手秋招转码指南

1.秋招情况介绍 1.1自我介绍 我是一名23届非科班转码选手,本硕均就读于某211院校机械专业,秋招共计拿下12份offer,包括大疆创新、海康威视、联发科技、理想汽车、中电28、阳光电源等各行业、各种性质企业的意向。主要的投递岗位为嵌入式软件…

若依微服务版在定时任务里面跨模块调用服务

第一步 在被调用的模块中添加代理 RemoteTaskFallbackFactory.java: package com.ruoyi.rpa.api.factory;import com.ruoyi.common.core.domain.R; import com.ruoyi.rpa.api.RemoteTaskService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springf…

【springmvc】执行流程

SpringMVC执行流程 原理图 1、SpringMVC常用组件 DispatcherServlet:前端控制器,不需要工程师开发,由框架提供 作用:统一处理请求和响应,整个流程控制的中心,由它调用其它组件处理用户的请求 HandlerMa…

Windows7,10使用:Vagrant+VirtualBox 安装 centos7

一、Vagrant,VirtualBox 是什么二、版本说明1、win7下建议安装版本2、win10下建议安装版本三、Windows7下安装1、安装Vagrant2、安装VirtualBox3、打开VirtualBox,配置虚拟机默认安装地址四、windows7下载.box文件,安装centos 71、下载一个.b…

拐点!新能源车交付均价首次「低于」燃油车,智能电动成新爆点

2023年开局,随着特斯拉打响新能源汽车市场的「价格战」首炮,除部分燃油车品牌(仍依赖自身多年的用户和品牌积累的溢价能力)没有跟进之外,几乎所有的新能源车型都在进行车型价格的下调。 而数据也在反映市场的拐点即将来…

深入理解Zookeeper的ZAB协议

ZAB是什么ZAB(Zookeeper Atomic Broadcast):Zookeeper原子广播ZAB是为了保证Zookeeper数据一致性而产生的算法(指的是Zookeeper集群模式)。它不仅能解决正常情况下的数据一致性问题,还可以保证主节点发生宕…

最全的论文写作技巧(建议收藏)

近10年来,笔者有幸多次参与教学论文的评审工作,在此,特将教学论文写作的步骤及相关问题整理汇总如下: 一、选定论题 (一)论题在文中的地位与作用 严格地讲,论文写作是从选定论题开始的。选题…

Android源码分析 - Parcel 与 Parcelable

0. 相关分享 Android-全面理解Binder原理 Android特别的数据结构(二)ArrayMap源码解析 1. 序列化 - Parcelable和Serializable的关系 如果我们需要传递一个Java对象,通常需要对其进行序列化,通过内核进行数据转发,…

这几个群,程序员可千万不要进!

震惊!某摸鱼网站惊现肾结石俱乐部! (图源V2EX) 无关地域、无关性别,各位程序员们在肾结石这个病上面有着出奇一致的反应。诸如此类的各种职业病在我们的生活中更是十分常见。 也可能是到年纪了,在办公室…

ATTCK v12版本战术介绍——提权(一)

一、引言在前几期文章中我们介绍了ATT&CK中侦察、资源开发、初始访问、执行、持久化战术理论知识及实战研究,通过实战场景验证行之有效的检测规则、防御措施,本期我们为大家介绍ATT&CK 14项战术中提权战术(一)&#xff0c…