草庐IT

并发编程AQS源码分析

鐡道 2023-04-17 原文

并发编程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);
    }
}

有关并发编程AQS源码分析的更多相关文章

  1. ruby - 寻找通过阅读代码确定编程语言的ruby gem? - 2

    几个月前,我读了一篇关于ruby​​gem的博客文章,它可以通过阅读代码本身来确定编程语言。对于我的生活,我不记得博客或gem的名称。谷歌搜索“ruby编程语言猜测”及其变体也无济于事。有人碰巧知道相关gem的名称吗? 最佳答案 是这个吗:http://github.com/chrislo/sourceclassifier/tree/master 关于ruby-寻找通过阅读代码确定编程语言的rubygem?,我们在StackOverflow上找到一个类似的问题:

  2. UE4 源码阅读:从引擎启动到Receive Begin Play - 2

    一、引擎主循环UE版本:4.27一、引擎主循环的位置:Launch.cpp:GuardedMain函数二、、GuardedMain函数执行逻辑:1、EnginePreInit:加载大多数模块int32ErrorLevel=EnginePreInit(CmdLine);PreInit模块加载顺序:模块加载过程:(1)注册模块中定义的UObject,同时为每个类构造一个类默认对象(CDO,记录类的默认状态,作为模板用于子类实例创建)(2)调用模块的StartUpModule方法2、FEngineLoop::Init()1、检查Engine的配置文件找出使用了哪一个GameEngine类(UGame

  3. 网络编程套接字 - 2

    网络编程套接字网络编程基础知识理解源`IP`地址和目的`IP`地址理解源MAC地址和目的MAC地址认识端口号理解端口号和进程ID理解源端口号和目的端口号认识`TCP`协议认识`UDP`协议网络字节序socket编程接口`sockaddr``UDP`网络程序服务器端代码逻辑:需要用到的接口服务器端代码`udp`客户端代码逻辑`udp`客户端代码`TCP`网络程序服务器代码逻辑多个版本服务器单进程版本多进程版本多线程版本线程池版本服务器端代码客户端代码逻辑客户端代码TCP协议通讯流程TCP协议的客户端/服务器程序流程三次握手(建立连接)数据传输四次挥手(断开连接)TCP和UDP对比网络编程基础知识

  4. ruby - 我正在学习编程并选择了 Ruby。我应该升级到 Ruby 1.9 吗? - 2

    我完全不是程序员,正在学习使用Ruby和Rails框架进行编程。我目前正在使用Ruby1.8.7和Rails3.0.3,但我想知道我是否应该升级到Ruby1.9,因为我真的没有任何升级的“遗留”成本。缺点是什么?我是否会遇到与普通gem的兼容性问题,或者甚至其他我不太了解甚至无法预料的问题? 最佳答案 你应该升级。不要坚持从1.8.7开始。如果您发现不支持1.9.2的gem,请避免使用它们(因为它们很可能不被维护)。如果您对gem是否兼容1.9.2有任何疑问,您可以在以下位置查看:http://www.railsplugins.or

  5. ruby - 如何以编程方式删除实例上的 "singleton information"以使其编码(marshal)? - 2

    我创建了一个由于“在运行时执行的单例元类定义”而无法编码的对象(这段代码的描述是否正确?)。这是通过以下代码执行的:#defineclassXthatmyusesingletonclassmetaprogrammingfeatures#throughcallofmethod:break_marshalling!classXdefbreak_marshalling!meta_class=class我该怎么做才能使对象编码正确?是否可以从对象instance_of_x的classX中“移除”单例组件?我真的需要一个建议,因为我们的一些对象需要通过Marshal.dump序列化机制进行缓存。

  6. Ruby 元编程问题 - 2

    我正在查看Ruby日志记录库Logging.logger方法并从sourceatgithub提出问题与这段代码有关:logger=::Logging::Logger.new(name)logger.add_appendersappenderlogger.additive=falseclass我知道类 最佳答案 这实际上删除了方法(当它实际被执行时)。这是确保close不会被调用两次的保障措施。看起来好像有嵌套的“class 关于Ruby元编程问题,我们在StackOverflow上找到一

  7. ruby-on-rails - 获取并发布相同匹配项的请求 - 2

    在我的路线文件中我有:match'graphs/(:id(/:action))'=>'graphs#(:action)'如果是GET请求(工作)或POST请求(不工作),我想匹配它我知道我可以使用以下方法在资源中声明POST请求:post'/'=>:show,:on=>:member但是我怎样才能为比赛做到这一点呢?谢谢。 最佳答案 如果你同时想要POST和GETmatch'graphs/(:id(/:action))'=>'graphs#(:action)',:via=>[:get,:post]编辑默认值可以设置如下match'g

  8. ruby - Paperclip:以编程方式分配图像并设置其名称 - 2

    使用Paperclip,我想从这样的URL抓取图像:require'open-uri'user.photo=open(url)问题是我最后得到一个像“open-uri20110915-4852-1o7k5uw”这样的文件名。有什么方法可以更改user.photo上的文件名?作为一个额外的变化,Paperclip将我的文件存储在S3上,所以如果我可以在初始分配中设置我想要的文件名就更好了,这样图像就会上传到正确的S3key。像这样:user.photo=open(url),:filename=>URI.parse(url).path 最佳答案

  9. ruby - 如何以编程方式检查证书是否已被吊销? - 2

    我正在开发一个xcode自动构建系统。在执行一些预构建验证时,我想检查指定的证书文件是否已被撤销。我了解securityverify-cert验证其他证书属性但不验证吊销。我如何检查撤销?我正在用Ruby编写构建系统,但我对任何语言的想法都持开放态度。我阅读了这个答案(Openssl-Howtocheckifacertificateisrevokedornot),但指向底部的链接(DoesOpenSSLautomaticallyhandleCRLs(CertificateRevocationLists)now?)进入的Material对我的目的来说有点过于复杂(用户上传已撤销的证书是一

  10. ruby - 如何保持我不常用的编程语言技能 - 2

    关闭。这个问题是off-topic.它目前不接受答案。想改进这个问题吗?Updatethequestion所以它是on-topic用于堆栈溢出。关闭11年前。Improvethisquestion我不经常使用ruby​​-通常它加起来相当于每两个月或更长时间编写一次脚本。我的大部分编程都是使用C++进行的,这与ruby​​有很大不同。由于我与ruby​​之间的差距如此之大,我总是忘记语言的基本方面(比如解析文本文件和其他简单的东西)。我想每天练习一些基本的东西,我想知道是否有一些我可以订阅的网站,并且会向我发送当天的Ruby问题或类似的东西。有人知道这样的站点/Internet服务吗?

随机推荐