并发编程AQS源码分析

AQS的全称为(AbstractQueuedSynchronizer),这个类在java.util.concurrent.locks包下面。它是一个Java提高的底层同步工具类,比如CountDownLatch、ReentrantLock,Semaphore,ReentrantReadWriteLock,SynchronousQueue,FutureTask等等皆是基于AQS的

简单来说:是用一个int类型的变量表示同步状态,并提供了一系列的CAS操作来管理这个同步状态对象
一个是 state(用于计数器,为0时释放锁)
一个是线程标记(当前线程是谁加锁的),
一个是阻塞队列Node(用于存放其他未拿到锁的线程)

例子:线程A调用了lock()方法,通过CAS将state赋值为1,然后将该锁标记为线程A加锁。如果线程A还未释放锁时,线程B来请求,会查询锁标记的状态,因为当前的锁标记为 线程A,线程B未能匹配上,所以线程B会加入阻塞队列,直到线程A触发了 unlock() 方法,这时线程B才有机会去拿到锁,但是不一定肯定拿到

源码分析阶段

直接看ReentrantLock中的lock方法加锁是如何使用到AQS的、ReentrantLock分为公平锁和非公平锁、默认是非公平锁

公平锁:按照队列先进先出的顺序进行加锁解锁

非公平锁:哪个线程先抢到锁就是哪个的、有可能造成某一个线程一直抢不到锁

// 非公平锁
final void lock() {
    // 进行cas操作、state值为0则赋值为1、成功获取锁
    if (compareAndSetState(0, 1))
        // 设置线程标记、线程标记用来检测是否是重入锁
        setExclusiveOwnerThread(Thread.currentThread());
    else
        // 调用AQS中的acquire方法、在调用ReentrantLock类下定义的Sync类中的nonfairTryAcquire方法进行具体的锁操作细节
        // 传参1、里面一直进行for循环CAS赋值、直到哪个线程抢到返回
        acquire(1);
}

// acquire方法是操作state状态位的方法、通过tryAcquire里面的CAS原子操作检测锁状态
public final void acquire(int arg) {
    // tryAcquire调用nonfairTryAcquire方法
    if (!tryAcquire(arg) &&
        // addWaiter: 根据给定模式创建一个当前线程的Node节点并返回、当前是创建一个独占锁的Node节点
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        // 中断当前线程
        selfInterrupt();
}

final boolean nonfairTryAcquire(int acquires) {
    // 先获取当前线程标识符、用于检测当前对象的线程标识符是否是同一个、同一个则为可重入锁、可直接返回、不是则返回后调用acquireQueued
    final Thread current = Thread.currentThread();
    // 获取计数器、表明当前线程重入了多少次锁
    int c = getState();
    // 为0则代表当前线程的锁全部解锁完毕
    if (c == 0) {
        // 给state计数器加1、代表上锁
        if (compareAndSetState(0, acquires)) {
            // 设置对象的线程标识符为当前线程
            setExclusiveOwnerThread(current);
            // 直接返回
            return true;
        }
    }
    // 代表当前有线程在使用该对象锁、检测是否是同一线程、是则进入、为可重入锁
    else if (current == getExclusiveOwnerThread()) {
        // 计算state
        int nextc = c + acquires;
        // 小于0则代表锁过多、即0x7fffffff + 1为负数
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        // 赋值state
        setState(nextc);
        // 直接返回
        return true;
    }
    // 返回false、表示当前对象锁被其他线程占用了、需要等到加锁的线程解锁才进行抢占
    return false;
}

//  根据给定模式创建一个当前线程的Node节点并返回
private Node addWaiter(Node mode) {
    // 根据当前线程创建一个Node节点、并传入模式(独占锁、共享锁)
    Node node = new Node(Thread.currentThread(), mode);
    // 获取队列尾部Node节点
    Node pred = tail;
    // 检测队列是否存在尾部节点、即是否存在节点
    if (pred != null) {
        // 新创建的Node节点上一个节点设为队列的尾部节点、然后下面直接将新创建节点插入队列尾部
        node.prev = pred;
        // 通过cas操作更换节点顺序、即新创建的节点为尾部、原先尾节点的下一个节点设置为当前新创建的Node节点
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    // 将新创建的Node节点插入队列尾部
    enq(node);
    return node;
}

// 里面for死循环无限调用nonfairTryAcquire方法检测锁是否被释放、然后进行抢占加锁
final boolean acquireQueued(final Node node, int arg) {
    // 错误位、当finally执行时该变量为true、则代表有异常发生、取消当前的操作
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            // 返回Node节点的上一个节点
            final Node p = node.predecessor();
            // 检测是否是头节点、是的话在进行cas检测锁是否解锁在抢占
            if (p == head && tryAcquire(arg)) {
                // 抢占到锁了、设置当前线程的Node节点为等待队列的头节点
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                // 检测当前线程是否调用了interrupt中断线程方法、并且检测状态位进行阻塞、调用LockSupport.park(this)方式阻塞自己
                parkAndCheckInterrupt())
                // 如果循环走到这里在return返回的话、代表线程将中断
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

Original: https://www.cnblogs.com/tie-dao/p/16667138.html
Author: 鐡道
Title: 并发编程AQS源码分析

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/621902/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • SPRINGBOOT(37)整合(8)JUNIT

    博客园 :当前访问的博文已被密码保护 请输入阅读密码: Original: https://www.cnblogs.com/qiu-hua/p/16552515.htmlAutho…

    Java 2023年5月30日
    092
  • MyBatis拦截器

    MyBatis拦截器的作用是在于Dao到DB中间进行额外的处理。大部分情况下通过mybatis的xml配置sql都可以达到想要的DB操作效果,然而存在一些类似或者相同的查询条件或者…

    Java 2023年6月7日
    077
  • Delphi线程简介—Create及其参数、Resume、Suspend和Terminate(转载)

    原文地址: TThread在Classes单元里的声明如下 先说一下TThread的Create的参数 当TThread的Create()被调用的时候,需要传递一个布尔型的参数Cr…

    Java 2023年5月29日
    075
  • 1_day01_java入门

    java入门 学习目标: 1.熟悉计算机编程语言 2.熟练掌握java特点 3.熟练配置java开发环境 4.熟练编写入门程序 5.熟练编写注释信息 一、计算机语言 1.1 什么是…

    Java 2023年6月8日
    065
  • day03_1_idea教程

    idea使用教程 一、idea相关概念介绍 1.1 IDE概念介绍 集成开发环境(IDE,Integrated Development Environment)是用于提供程序开发环…

    Java 2023年6月8日
    065
  • android多文件上传,java服务端接收

    Android多文件上传,java服务端接收 1、Android端 代码: String uploadUrl = "http://xxx/uploadFiles&quot…

    Java 2023年6月5日
    077
  • 面试官:你说说一条更新SQL的执行过程?

    在上一篇《面试官:你说说一条查询SQL的执行过程?》中描述了Mysql的架构分层,通过解析器、优化器和执行引擎完成一条SQL查询的过程,那这一篇续上继续说明一条更新SQL的执行过程…

    Java 2023年6月13日
    077
  • 测试用例千万不能随便,记录由一个测试用例异常引起的思考

    一 测试用例大家平时写不写? 我以前写测试用例只是针对业务接口,每个接口写一个,数据case也只是测一种。能跑通就可以了。要不同的场景case,那就改数据。重新跑一遍。简单省事。 …

    Java 2023年6月8日
    092
  • 20220809-Java的接口和实现interface&implements

    1.接口的语法 2.接口随版本的变化 3.接口注意事项 4.实现接口 VS 继承类 5.接口的多态特性: 6.接口代码示例 今天抽空学习了接口相关的基础知识,学习了一些新的名词:接…

    Java 2023年6月15日
    084
  • Spring Cloud Ribbon客户端负载均衡(四)

    序言 Ribbon 是一个客户端负载均衡器(Nginx 为服务端负载均衡),它赋予了应用一些支配 HTTP 与 TCP 行为的能力,可以得知,这里的客户端负载均衡也是进程内负载均衡…

    Java 2023年5月30日
    069
  • JavaWeb开发——软件国际化(动态元素国际化)

    软件国际化的第二个部分,就是动态元素国际化。 数值,货币,时间,日期等数据由于可能在程序运行时动态产生,所以无法像文字一样简单地将它们从应用程序中分离出来,而是需要特殊处理。Jav…

    Java 2023年5月29日
    077
  • MongoDB安装使用教程

    MongoDB安装使用教程 介绍 MongoDB是一个基于分布式文件存储的数据库,是一个文档数据库,支持的数据结构非常松散,是类似json的bson格式,可以存储比较复杂的数据类型…

    Java 2023年6月15日
    088
  • Json 序列化框架导致 CPU 使用率过高

    问题现象:CPU 负载过高 我们线上的 jenkins 系统,时不时会发生 CPU 负载过高的现象。 CPU 负载过高后,SRE 同学会收到电话告警。 在我们的监控系统中,可以看到…

    Java 2023年6月9日
    083
  • Spring Authorization Server授权服务器入门

    11月8日Spring官方已经强烈建议使用 Spring Authorization Server替换已经过时的Spring Security OAuth2.0,距离 Spring…

    Java 2023年5月30日
    096
  • 程序员要知道的22个学习网站

    点击标题即可直达链接网址 GitHub是一个面向开源及私有软件项目的托管以及在线软件开发平台,用于存储、跟踪和协作软件项目,开发者能够上传自己的代码文件,并与其他开发者在开源项目上…

    Java 2023年6月15日
    070
  • MySQL中读页缓冲区buffer pool

    Buffer pool 我们都知道我们读取页面是需要将其从磁盘中读到内存中,然后等待CPU对数据进行处理。我们直到从磁盘中读取数据到内存的过程是十分慢的,所以我们读取的页面需要将其…

    Java 2023年6月16日
    081
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球