【并发编程】AQS源码

ReentrantLock

互斥锁,可重入
AQS是可以支持互斥锁和共享锁的,这里只分析互斥锁的源码

加锁

公平锁和非公平锁

  • 公平锁
	final void lock() {
		acquire(1); //抢占1把锁.
	}
	
	// AQS里面的方法
	public final void acquire(int arg) { 
		if (!tryAcquire(arg) &&
			acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
			selfInterrupt();
		}
	protected final boolean tryAcquire(int acquires) {
		final Thread current = Thread.currentThread();
		int c = getState();
		if (c == 0) { //表示无锁状态
			if (!hasQueuedPredecessors() &&
				compareAndSetState(0, acquires)) { //CAS原子操作	
				setExclusiveOwnerThread(current); //把获得锁的线程保存到exclusiveOwnerThread中
				return true;
			}
		}
		//如果当前获得锁的线程和当前抢占锁的线程是同一个,表示重入
		else if (current == getExclusiveOwnerThread()) {
		    //增加重入次数.
			int nextc = c + acquires; 
			if (nextc < 0)
				throw new Error("Maximum lock count exceeded");
			setState(nextc); //保存state
			return true;
		}
		return false;
	}
  • 非公平锁
    final void lock() {
        //非公平锁,不管当前AQS队列中是否已有线程在排队的,都先去插队(尝试获得锁)
        if (compareAndSetState(0, 1)) //返回false表示抢占锁失败
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }
    //AQS里的方法
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
                acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
    }
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            //hasQueuedPredecessors 和公平锁的区别,公平锁是如果已经有在排队的线程了,
            // 那么新过来的线程就不允许插队(去尝试获取锁)
            if (compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
  • 加入队列并进行自旋等待
public final void acquire(int arg) {
    //如果尝试获取锁失败,则会加入队列并进行自旋等待
	if (!tryAcquire(arg) &&
		acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
		selfInterrupt();
}

acquireQueued(addWaiter(Node.EXCLUSIVE), arg)

  • addWaiter(Node.EXCLUSIVE) -> 添加一个互斥锁的节点
  • acquireQueued() -> 自旋锁和阻塞的操作
    private Node addWaiter(Node mode) {
        //把当前线程封装成一个Node节点。后续唤醒线程的时候,需要得到被唤醒的线程.
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        //假设不存在竞争,那么一次CAS操作就可以将Node节点加入到双向链表,如果存在竞争,最终还是要通过enq里的自旋来加入到双向链表中
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        //第一次添加Waiter的时候pred一定为空,会直接执行enq方法
        enq(node);
        return node;
    }
    //从尾部添加到链表,尾插法
    private Node enq(final Node node) {
        //自旋
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                //初始化一个head节点,注意Head节点是一个空节点,不存储任何线程信息
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                //注意这里先设置的新节点node的prev,所以后续在遍历链表的时候都是从tail->head
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

在这里插入图片描述

    //node表示当前来抢占锁的线程节点,可能是Thread B,Thread C
    final boolean acquireQueued(final Node node, int arg) {
        //标记是否成功拿到锁
        boolean failed = true;
        try {
            //标记等待过程中是否中断过
            boolean interrupted = false;
            for (;;) { //自旋
                //begin
                //获取当前节点的前置节点,如果是head(则表示这个线程是排在第一个),会尝试去获取锁
                //tryAcquire 公平锁里判断当前节点是否有前置节点来决定能否去抢占锁
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    //拿到资源后,将head指向该结点。
                    setHead(node);
                    //setHead中node.prev已置为null,此处再将head.next置为null,就是为了方便GC回收以前的head结点。
                    p.next = null;
                    failed = false;
                    //返回等待过程中是否被中断过
                    return interrupted;
                }
                //end begin->end 这里会尝试去获得锁,如果失败的话会让线程去阻塞(park),如果设置前驱节点节点的signal状态失败,那么会再次尝试去获取锁,失败后再次尝试设置(自旋)
                if (shouldParkAfterFailedAcquire(p, node) &&
                        parkAndCheckInterrupt()) //LockSupport.park
                    //如果等待过程中被中断过,就将interrupted标记为true,注意此时的failed=true,因为是通过中断唤醒的,并没有获取到锁资源
                    interrupted = true;
            }
        } finally {
            //如果等待过程中没有成功获取资源(如timeout,或者可中断的情况下被中断了),那么取消结点在队列中的等待。
            if (failed)
                cancelAcquire(node);
        }
    }

先来看下shouldParkAfterFailedAcquireparkAndCheckInterrupt()的实现再来看acquireQueued的代码,Node节点的状态有如下几种:

  • SIGNAL: -1 表示它的下一个节点处于park状态,如果当前节点取消或者释放锁资源的时候需要unpark下一个节点
  • CANCELLED: 1 :当前节点由于超时或者被中断而处于取消状态
  • CONDITION:-2 共享锁使用的状态
  • PROPAGATE: -3
    默认为0
 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            //前驱节点已经是SIGNAL状态了,当前节点可以安心去park
            return true;
        if (ws > 0) {
            //如果前驱节点处于CANCELLED状态,那么就需要遍历链表(tail->head),直到找到最近一个正常状态的节点,排在其后
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            //如果前驱节点非CANCELLED状态,那就把前驱节点的状态设置成SIGNAL(告诉他等他释放锁资源需要通知自己) 并发场景下CAS可能会失败(失败后会在外层方法自旋,再次触发该方法)
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

这里之所以通过 node.prev = pred = pred.prev; 从后往前遍历,去掉为CANCELLED状态的节点,因为我们在设置双向链表的时候是先设置的tail的prev节点,如果从前往后操作,并发场景下可能会出现next指针未指向的情形,导致异常

在整个过程中,如果前驱结点的状态不是SIGNAL,那么自己就不能安心去park,需要去找个安心的休息点,同时可以再尝试下看有没有机会获取锁资源。

    //ThreadB、 ThreadC -> 都会阻塞在下面这个代码的位置.
    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this); //调用park()使线程进入waiting状态
        return Thread.interrupted(); //如果被唤醒,查看自己是不是被中断唤醒的。线程除了被正常唤醒之外,interrupt方法也会唤醒线程 Thread.interrupted()判断当前线程是否被中断过
    }
 

再来总结下acquireQueued()的流程:
node进入队尾后
1.先判断前驱节点是否为head,如果是则尝试去获取锁资源,拿到锁资源后就将head指向当前节点
2. 获取不到锁资源则检查前驱节点的状态,找到安全休息点(前驱节点为SIGNAL状态)则调用park进入等待状态
3.被唤醒后,先判断从入队到唤醒过程中是否有被中断过,如果有的话则将本节点置为CANCELLED状态,否则尝试去获取锁资源,执行步骤1。

解锁

   public final boolean release(int arg) {
        if (tryRelease(arg)) {
            //得到当前AQS队列中的head节点
            Node h = head; 
            //head节点不为空且状态不是0,说明这个节点不是链表里的最后一个节点
            if (h != null && h.waitStatus != 0) 
                //唤醒下一个节点(在里面会调用unpark方法)
                unparkSuccessor(h); 
            return true;
        }
        return false;
    }

    private void unparkSuccessor(Node node) {
        int ws = node.waitStatus;
        if (ws < 0) //SIGNAL状态,表示为可以唤醒状态
            compareAndSetWaitStatus(node, ws, 0); //恢复成0
        //查找下一个需要唤醒的结点s
        Node s = node.next;
        //说明ThreadB这个线程可能已经被销毁,或者出现异常...
        if (s == null || s.waitStatus > 0) {
            s = null;
            //从tail -> head进行遍历,进行节点的移除
            for (Node t = tail; t != null && t != node; t = t.prev)
                //查找到小于等于0的节点(我们之前修改节点的状态是将前驱节点设置为SIGNAL,所以最后一个节点的状态是默认的0)
                if (t.waitStatus <= 0) 
                    s = t;
        }
        if (s != null)
            //唤醒封装在Node中的被阻塞的线程)
            LockSupport.unpark(s.thread); 
       
    }
    
     protected final boolean tryRelease(int releases) {
          int c = getState() - releases;
          if (Thread.currentThread() != getExclusiveOwnerThread())
              throw new IllegalMonitorStateException();
          boolean free = false;
          if (c == 0) {
              free = true;
              setExclusiveOwnerThread(null);
          }
          setState(c);
          return free;
     }

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

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

相关文章

IP协议(网络层重点协议)

目录 一、IP协议报头格式 二、地址选择 1、IP地址 &#xff08;1&#xff09;格式 &#xff08;2&#xff09;组成 &#xff08;3&#xff09;分类 &#xff08;4&#xff09;子网掩码 三、路由选择 IP协议是网络层的协议&#xff0c;它主要完成两个方面的任务&#xf…

redis基础(6.0)数据结构、事务、常用组件等

1 概述 1.1 redis介绍 Redis 是互联网技术领域使用最为广泛的存储中间件&#xff0c;它是「Remote Dictionary Service」的首字母缩写&#xff0c;也就是「远程字典服务」。Redis 以其超高的性能、完美的文档、 简洁易懂的源码和丰富的客户端库支持在开源中间件领域广受好评。…

车载网络 - Autosar网络管理 - 常用缩写

为了方便大家日常工作中的使用和交流&#xff0c;每块专业规范或者文章中&#xff0c;都会有或多或少的缩写使用&#xff0c;然而如果一段时间没使用&#xff0c;经常会忘记这些缩写到底代表的是什么意思&#xff0c;为了方便后续内容的介绍&#xff0c;也为了我自己后面忘记后…

做自动化测试时所谓的“难点”

这篇关于自动化测试的文章&#xff0c;可能和你看到的大多数自动化的文章有所不同。我不是一位专职的自动化测试工程师&#xff0c;没有开发过自动化的工具或者框架&#xff0c;用的自动化的工具也不多&#xff0c;也没有做过开发&#xff0c;所以我讲不出那些现在很多人很看重…

JavaScript【一】JavaScript变量与数据类型

文章目录&#x1f31f;前言&#x1f31f;变量&#x1f31f; 变量是什么&#xff1f;&#x1f31f; 变量提升&#x1f31f; 声明变量&#x1f31f; JavaScript有三种声明方式&#x1f31f; 命名规范&#x1f31f; 注意&#x1f31f;数据类型以及运算&#x1f31f; 检测变量数据类…

数据智能服务商奇点云完成近亿元C2轮融资

奇点云集团宣布已于2022年底完成近亿元C2轮融资&#xff0c;余杭国投领投&#xff0c;中银渤海基金跟投。 截至目前&#xff0c;奇点云共获近3亿元C轮融资。C轮领投方包括泰康人寿&#xff08;旗下泰康资产执行&#xff09;、余杭国投&#xff0c;跟投方包括字节跳动、德同资本…

app抓包实战

文章目录一、抓包原理二、常用应用场景三、过滤四、重发五、修改请求六、断点&#xff08;BreakPoint&#xff09;一、抓包原理 二、常用应用场景 解决移动端接口测试 解决接口测试过程中检查传参错误问题 mock测试&#xff08;虚拟的对象代替正常的数据、后端接口没有开发完成…

QML控件--DialogButtonBox

文章目录一、控件基本信息二、控件使用三、属性成员四、附加属性成员五、成员函数六、信号一、控件基本信息 Import Statement&#xff1a;import QtQuick.Controls 2.14 Since&#xff1a;Qt 5.8 Inherits&#xff1a;Container 二、控件使用 DialogButtonBox&#xff1a;是…

攻防世界-web-easyupload

题目描述&#xff1a;一名合格的黑客眼中&#xff0c;所有的上传点都是开发者留下的后门 很简单的一个上传图片的界面。 我们先正常上传一个图片&#xff0c;从提示信息中可以看出我们是上传到了uploads目录下 然后通过bupsuite抓包修改请求&#xff0c;将文件名修改为1.php&a…

Java垃圾回收机制GC完全指南,让你彻底理解JVM运行原理

1、GC过程 1&#xff09;先判断对象是否存活(是否是垃圾) 可以通过引用计数算法和可达性分析算法来判断&#xff0c;由于引用计数算法无法解决循环引用的问题&#xff0c;所以目前使用的都是可达性分析算法 2&#xff09;再遍历并回收对象(回收垃圾) 可以通过垃圾收集器&…

三、Locust任务(task)详解

当一个负载测试开始时&#xff0c;将为每个模拟用户创建一个用户类的实例&#xff0c;他们将在自己的绿色线程中开始运行。当这些用户运行时&#xff0c;他们会选择执行的任务&#xff0c;睡眠一段时间&#xff0c;然后选择一个新的任务&#xff0c;如此循环。 这些任务是正常…

什么是自动化测试?自动化测试现状怎么样?

什么是自动化测试&#xff1a;其实自动化测试&#xff0c;就是让我们写一段程序去测试另一段程序是否正常的过程&#xff0c;自动化测试可以更加省力的替代一部分的手动操作。 现在自动化测试的现状&#xff0c;也是所有学习者关心的&#xff0c;但现在国内公司主要是以功能测…

Flash Linux to eMMC

实验目的&#xff1a;从eMMC启动Linux系统 Step1:确定eMMC被挂在哪个设备 哪个设备含有boot0分区和boot1分区&#xff0c;就是eMMC。实验中是位于mmcblk1上。 rootam64xx-evm:~# ls -l /dev/mmcblk* brw-rw---- 1 root disk 179, 0 Feb 27 13:25 /dev/mmcblk0 brw-rw---- …

10 kafka生产者发送消息的原理

1.发送原理&#xff1a; 在消息发送的过程中&#xff0c;涉及到了两个线程——main 线程和 Sender 线程。在 main 线程 中创建了一个双端队列 RecordAccumulator。main 线程将消息发送给 RecordAccumulator&#xff0c; Sender 线程不断从 RecordAccumulator 中拉取消息发送到…

【机器学习】P10 从头到尾实现一个线性回归案例

这里写自定义目录标题&#xff08;1&#xff09;导入数据&#xff08;2&#xff09;画出城市人口与利润图&#xff08;3&#xff09;计算损失值&#xff08;4&#xff09;计算梯度下降&#xff08;5&#xff09;开始训练&#xff08;6&#xff09;画出训练好的模型&#xff08;…

离散数学_第二章:基本结构:集合、函数、序列、求和和矩阵(1)

集合与函数2.1 集合 2.1.1 集合的基本概念 2.1.2 集合的表示方法 2.1.3 文氏图 2.1.4 证明集合相等 2.1.5 集合的大小 ——基 2.1.6 幂集 2.1.7 集族、指标集 2.1.8 笛卡尔积 2.1.9 容斥原理2.1 集合 2.1.1 集合的基本概念 定义1&#xff1a;集合 是不同对象的一个无序的聚…

【SpringCloud系列】开发环境下重写Loadbalancer实现自定义负载均衡

前言 spring-cloud-starter-netflix-ribbon已经不再更新了&#xff0c;最新版本是2.2.10.RELEASE&#xff0c;最后更新时间是2021年11月18日&#xff0c;详细信息可以看maven官方仓库&#xff1a;https://search.maven.org/artifact/org.springframework.cloud/spring-cloud-st…

Windows环境下实现设计模式——职责链模式(JAVA版)

我是荔园微风&#xff0c;作为一名在IT界整整25年的老兵&#xff0c;今天总结一下Windows环境下如何编程实现职责链模式&#xff08;设计模式&#xff09;。 不知道大家有没有这样的感觉&#xff0c;看了一大堆编程和设计模式的书&#xff0c;却还是很难理解设计模式&#xff…

Maven的进阶操作

系列文章目录 Maven进阶操作的学习 文章目录系列文章目录前言一、分模块开发与设计二、依赖管理1.依赖传递2.可选依赖3.排除依赖三、继承与聚合1.聚合2.继承四、属性1.属性2.版本管理五、多环境配置与应用1.多环境开发2.跳过测试六、私服1.私服简介2.私服仓库分类3.资源上传与…

比GPT-4 Office还炸裂,阿里版GPT全家桶来袭

目录 【新智元导读】 文案、策划、邮件&#xff0c;一键搞定 不用写代码&#xff0c;草稿秒变小程序 聊天记录不用翻&#xff0c;摘要自动生成 会上开小差&#xff1f;不怕&#xff0c;AI替你记了 AI版十万个为什么&#xff0c;有问必答 剁手买买买&#xff0c;连手都不…