AQS源码分析——以ReentrantLock为例

news2025/1/12 3:53:30

AQS自身属性:

private transient volatile Node head;
private transient volatile Node tail;
private volatile int state;

Node属性:

//	共享
static final Node SHARED = new Node();
//	独占       
static final Node EXCLUSIVE = null;
// 线程被取消了        
static final int CANCELLED =  1;	
//	后继线程需要唤醒      
static final int SIGNAL    = -1;
//	等待condition唤醒        
static final int CONDITION = -2;
//	共享式同步状态获取将会无条件地传播下去       
static final int PROPAGATE = -3;
//	初始为0,状态是上面的几种
volatile int waitStatus;		//	很重要
//	前置节点
volatile Node prev;
//	后置节点
volatile Node next;

volatile Thread thread;
  1. 1. 以ReentrantLock为例,其他类举一反三,方法lock()

  2. 2. Lock接口实现类,基本都是通过【聚合】了一个 【队列同步器】的子类完成线程访问控制的Sync实现lock;Sync继承AQS;Sync又有两个实现类。

  3. 
    //	公平与非公平锁的构造
    public ReentrantLock() {
           sync = new NonfairSync();
     }
     
    public ReentrantLock(boolean fair) {
           sync = fair ? new FairSync() : new NonfairSync();
    }
     

     公平锁与非公平锁的实现:

  4. 1.0 
    //	非公平锁
    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;
    
        final void lock() {
            if (compareAndSetState(0, 1))	//	这里一开始省略了acquire,能抢到直接抢,抢不到再排队
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }
    
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);//	实现展开在下面
        }
    }
    
    1.1 
    //	非公平锁的 tryAcquire实现细节
    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {	//	这里判断状态,1是被占用,0是不被占用。再尝试抢一下,如果正好线程释放锁那么就正好抢到
            if (compareAndSetState(0, acquires)) { //	这里和公平锁相比缺少了一个hasQueuedPredecessors()
                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;		//	即不空闲也不是自己占用锁,则返回false
    }
    
    
    2.0
    //	公平锁:
    
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;
        final void lock() {
            acquire(1);
          //	acquire方法 -> tryAcquire(arg) -> protected boolean tryAcquire(int arg)
          //	tryAcquire是父类定义的,这里使用了模版设计模式
          //	见下面的函数:2.1
        }
        protected final boolean tryAcquire(int acquires) {	//	tryAcquire的实现
            final Thread current = Thread.currentThread();
            int c = getState();		//	获取状态位
            if (c == 0) {		
                if (!hasQueuedPredecessors() && 		//	公平锁分支,公平与非公平就这里不一样,详细在下面2.2
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            } else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }
    
    2.1 
    //	父类不实现,下放到子类实现
    //	子类有很多实现类,这里参考FairSync的实现,代码在上面
    protected boolean tryAcquire(int arg) {
         throw new UnsupportedOperationException();
    }
    
    2.2 
    //	hasQueuedPredecessors
    //	逻辑很简单,就是判断一下队列前面是否有排队线程,如果返回true,说明有一个排队的线程;返回false说明这个线程是队列的头
    public final boolean hasQueuedPredecessors() {
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        return h != t && ((s = h.next) == null || s.thread != Thread.currentThread());
    }

    接下来以非公平锁为突破口:

  5. 1. 对比公平锁和非公平锁的 tryAcquire()方法的实现代码,其实差别就在于非公平锁获取锁时比公平锁中少了一个判断hasQuevedPredecessors()

  6. 2. hasQueuedPredecessors() 中判断了是否需要排队,导致公平锁和非公平锁的差异如下:

    1. 1. 公平锁:公平锁讲究先来先到,线程在获取锁时如果这个锁的等待队列中己经有线程在等待,那么当前线程会进入等待队列中

    2. 2. 非公平锁:不管是否有等待队列,如果可以获取锁,则立刻占有锁对象。也就是说队列的第一个排队线程苏醒后,不一定就是排头的这个线程得锁,它还是需要参加竞争锁(存在线程竞争的情况下),后来的线程可能不讲武德插队夺锁了。

 

 

引出源码三部曲:

  1. 1. tryAcquire

  2. 2. addWaiter

  3. 3. acquireQueued

lock方法:

//	对比公平与非公平的lock方法:

//	公平锁lock
final void lock() {
    acquire(1);
}


//	非公平锁
final void lock() {
    if (compareAndSetState(0, 1))			//	CAS操作直接抢锁
        setExclusiveOwnerThread(Thread.currentThread());	//	设置拥有独占量的线程
    else
        acquire(1);		//	抢不到再去排队
}
//	这里非公平的复杂,我们看非公平锁的代码:

acquire三大流向:

 

1.0 acquire方法
//	公平与非公平的acquire方法一样的
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&		//	tryAcquire方法是再次尝试抢一下锁,看看是否空闲或者自己持有锁,具体实现在前面的代码里。这里如果失败就是false,取反就是true,继续往下走,执行addWaiter
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))	//	Node.EXCLUSIVE表示独占排他
        selfInterrupt();
}


2.0 tryAcquire方法
//	acquire方法第一个执行的方法,也是分支的第一支
//	顶层父类并未实现改方法,AQS类中
protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}
//	公平锁和非公平锁都对此方法进行了实现,非公平只是缺少了hasQueuedPredecessors()方法,代码在前面有展示


3.0 addWaiter方法
//	如果执行2.0抢锁失败

private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);		//	封装线程,进入队列
    Node pred = tail;		//	设置前置为tail
    if (pred != null) {	//	如果pred不为空,意思就是不是队列的第一个元素,执行下列操作
        node.prev = pred;			//	更新前置节点
        if (compareAndSetTail(pred, node)) {	// 3.0.1CAS的修改尾部元素,并且直接返回node,不再执行enq入队操作
            pred.next = node;
            return node;
        }
    }
    enq(node);	//	进入队列,当且仅当node是pred==null时才会执行
    return node;
}

3.1 enq方法:
//	入队操作:
private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) { // 如果tail为空,则需要初始化,这里头节点并没有使用实参node,而是new了一个空节点,
            if (compareAndSetHead(new Node()))	//	CAS的修改队首,期望值为null,改为空节点,也叫虚拟节点,作用就是站位
                tail = head;		//	将头尾指针都指向这个节点
        } else {					//	如果tail不为空(可能一开始tail为空,但是是一个for true循环,第一次初始化之后就会走这一步)
            node.prev = t;		//	将t设置为node节点的前置节点,
            if (compareAndSetTail(t, node)) {		//CAS的修改尾节点,这部分代码逻辑和3.0.1 逻辑一样
                t.next = node;
                return t;
            }
        }
    }//	注意这是个循环,保证这个里面一定会执行到else,
}

4.0 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)) {	//	如果前置节点是head,再次尝试获取抢锁
                setHead(node);		//	细节见4.1 
                p.next = null; 		// 	意思是将获取到锁的节点变成虚拟头节点,之前的虚拟头节点废弃掉,直接GC
                failed = false;		//	入队成功,则改为false
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) && 	//	细节见4.2 既改变状态,又返回boolean值
                parkAndCheckInterrupt())	//	细节见4.3  等待lockSupport发放许可
                interrupted = true;			//	修改interrupted为true
        }
    } finally {
        if (failed)		//	如果入队失败,则取消排队
            cancelAcquire(node);
    }
}

4.1 setHead
  
private void setHead(Node node) {
    head = node;
    node.thread = null;
    node.prev = null;
}


4.2 shouldParkAfterFailedAcquire方法
//	
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { //	此处pred就是head节点
    int ws = pred.waitStatus;		//	head节点状态默认为wait 0
    if (ws == Node.SIGNAL)			//	如果是-1则执行
        return true;
    if (ws > 0) {								//	大于0
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {									//	status = 0跑到这里来
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);		//	后面的节点将他前面的节点status改为-1
    }
    return false;
}


4.3 parkAndCheckInterrupt
//	发放许可并不是卡住线程的关键,他只是保证,进入队列的线程节点一定是按照顺序一个个执行的
private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);					//	从acquireQueued方法过来,如果没有获得许可就一直在这卡着
    return Thread.interrupted();		//	正常执行,没有意外或被打断就是false,跳转到4.0中继续
}

 unlock方法:

0.0 unlock
//	
public void unlock() {
    sync.release(1);
}

1.0 release
//	释放锁
public final boolean release(int arg) {
    if (tryRelease(arg)) {		//	细节见1.1 此时执行结果为true
        Node h = head;		//	获取头节点
        if (h != null && h.waitStatus != 0)		//	头节点不等于null,且头节点wait!=0,(之前修改为SIGNAL -1)
            unparkSuccessor(h);	//	细节见1.2,传入参数为头节点
        return true;
    }
    return false;
}

1.1 
//	tryRelease
protected final boolean tryRelease(int releases) {	// releases = 1	
    int c = getState() - releases;	//	c =0
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {		//	进入这里
        free = true;		//	free = true
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;			//	返回true
}


1.2 unparkSuccessor
//	
private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    if (ws < 0)		
        compareAndSetWaitStatus(node, ws, 0);		//	头节点进来之后,会将wait的-1改为0

    Node s = node.next;		//	获取头节点的下一个节点,即第一个真实任务节点
    if (s == null || s.waitStatus > 0) {		//		节点不为null,不进来
        s = null;					
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)		//	节点不为null,进入这里
        LockSupport.unpark(s.thread);		//	给线程节点发放许可,继续进入到1.0中;此处受影响的还有acquire方法代码块的4.3
				//	不过发放许可并不代表中一定是这个线程获取锁,这就是非公平锁的精髓,总会有不讲武德插队的,获取许可只是有获取锁的可能,是否真的获取到还要看能否抢到锁。这里发放许可,使得被park的线程唤醒了,可以去tryAcquire操作,依然可能会失败,继续被阻塞
  			//		这里如果不使用park和unpark,则会一直执行for,导致cpu负载过重
}

异常情况

模拟某个线程不想等待了,想取消排队

  1. 1. 队尾元素离开

  2. 2. 队中间的元素离开

  3. 3. 队中间多个元素离开

    1.0 cancelAcquire方法
    //	取消入队
    private void cancelAcquire(Node node) {
        // Ignore if node doesn't exist
        if (node == null)
            return;
    
        node.thread = null;		//	将线程赋值null
    
        Node pred = node.prev;	//	获取前一个节点
        while (pred.waitStatus > 0)		//	waitStatus>0意思就是取消状态,一直向前获取到一个不会取消的头节点,
            node.prev = pred = pred.prev;		//	最坏情况就是获取到虚拟头节点
    
        Node predNext = pred.next;		//	前置节点的next指向当前节点的next
    
        node.waitStatus = Node.CANCELLED;		//	状态设置为取消
    
        if (node == tail && compareAndSetTail(node, pred)) {		//	如果node是队尾节点,CAS将pre节点设置为尾节点
            compareAndSetNext(pred, predNext, null);		//	同时CAS设置pred节点的next为null
        } else {					//	如果不是队尾节点
            int ws;
            if (pred != head &&		//	如果前置节点不是头节点且
                ((ws = pred.waitStatus) == Node.SIGNAL ||	//前置节点的状态为-1或状态小于0并且修改为-1成功
                 (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&	
                pred.thread != null) {	//	且pred线程不为空
                Node next = node.next;	//	获取当前节点的next	
                if (next != null && next.waitStatus <= 0)		//	next不为空且status<=0时
                    compareAndSetNext(pred, predNext, next);
            		} else {
                unparkSuccessor(node);
            }
            node.next = node; // help GC
        }
    }

     

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

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

相关文章

360手机黑科技“位置穿越”功能修复 360手机位置穿越不能用了 360手机刷机

360手机黑科技“位置穿越”功能修复 360手机位置穿越不能用了 360手机刷机 参考&#xff1a;360手机-360刷机360刷机包twrp、root 360刷机包360手机刷机&#xff1a;360rom.github.io 【前言】 360手机&#xff0c;内置的黑科技“位置穿越”&#xff0c;引用高德地图&#xff…

基于NSGA-II算法的多目标多旅行商问题建模求解

基于NSGA-II算法的多目标多旅行商问题建模求解 1引言2多目标多旅行商问题3多目标遗传算法NSGA-II3.1 编码3.2选择&#xff08;锦标赛选择&#xff09;3.3 交叉&#xff08;顺序交叉&#xff09;3.4 变异3.5快速非支配排序3.5.1符号说明3.5.2快速非支配排序[^7][^6]3.5.3快速非支…

C语言王国探险记之变量的前世今生

王国探险记系列 文章目录&#xff08;2&#xff09; 前言 一、变量是什么&#xff1f; 二、变量如何定义&#xff1f; 注意&#xff1a;当你赋值小数的时候&#xff0c;编译器会自然认为你是一个double类型的&#xff0c;所以你在使用float类型的时候要在小数后面加个f&…

Go语言基础:基本数据类型

一、整型 1. 整型分类 整型分为以下两个大类&#xff1a; &#xff08;1&#xff09;按长度分为&#xff1a;int8、int16、int32、int64 &#xff08;2&#xff09;对应的无符号整型&#xff1a;uint8、uint16、uint32、uint64 其中&#xff0c;uint8就是我们熟知的byte型&…

关于spring mvc 的ViewResolver的问题

今天在一个项目使用springboot的spring mvc的时候遇到了一个问题&#xff1a;在controller中使用返回字符串redirect:/xxxx/xxx进行重定向失败&#xff0c;出现了无法解析redirect:/xxxx/xxx。 找了半天才发现视图解析器ViewResolver中没有添加InternalResourceViewResolver 项…

微服务(总): eureka与nacos的区别及心跳配置修改

目录 简介: 1. nacos与eureka的区别 1.0 功能方面 1.1 连接方式不同 1.2 服务异常后多久剔除 1.2.1 eureka介绍: 1.2.2 nacos介绍: 1.3 操作的方式 1.3.1 nacos 专门的可视化界面(如图) 1.3.2 eureka 比较简约(如图) 1.4 保护机制介绍 1.4.1 CAP原则简介:(取自百…

Redis入门(5)-set

Redis中set的元素具有无序性与不可重复性 1.sadd key member[member] 添加元素&#xff0c;若元素存在返回0若不存在则添加 sadd DB mysql oracle sadd DB mysql sadd DB db22.smembers key 查看set中所有元素 smembers DB3.sismember key member 判断元素在set中是否存…

好书精读】网络是怎样连接的 —— 信号在网线和集线器中传输

&#xff08; 该图由我使用 AI 绘制 &#xff09; 目录 每个包都是独立传输的 防止网线中的信号衰减很重要 “双绞”是为了抑制噪声 集线器将信号发往所有线路 每个包都是独立传输的 从计算机发送出来的网络包会通过集线器 、 路由器等设备被转发 &#xff0c; 最 终到达…

智安网络|弹性网络与网络安全:保卫数字世界的关键联结

随着数字化时代的来临&#xff0c;弹性网络和网络安全成为了信息交流和数据传输的关键基础。弹性网络为用户提供了高度可靠、灵活可扩展和强大的网络基础设施&#xff0c;而网络安全则旨在保护这个网络生态系统不受威胁。 弹性网络的定义与重要性 弹性网络的重要性&#xff1a…

【Leetcode60天带刷】day36——56. 合并区间,738.单调递增的数字

​ 题目&#xff1a; 56. 合并区间 以数组 intervals 表示若干个区间的集合&#xff0c;其中单个区间为 intervals[i] [starti, endi] 。请你合并所有重叠的区间&#xff0c;并返回 一个不重叠的区间数组&#xff0c;该数组需恰好覆盖输入中的所有区间 。 示例 1&#xff1a;…

python基础学习9【MinMaxScale()、StandScale()、DecimalScale、transformer】

标准化数据【离差标准化数据、标准差标准化数据、小数定标标准化数据】 离差标准化数据&#xff1a; 数据的整体分布情况并不会随离差标准化而发生改变&#xff0c;原先取值较大的数据&#xff0c;在做完离差标准化后的值依旧较大&#xff1b; 对原始数据的一种线性变换&…

模拟电路系列分享-运放的关键参数

目录 概要 整体架构流程 技术名词解释 1.输入失调电压 2.输入失调电压对电路的影响 3.数据手册中关于失调电压的描述 技术细节 小结&#xff1a; 概要 提示&#xff1a;这里可以添加技术概要 实际运放与理想运放具有很多差别。理想运放就像一个十全十美的人&#xff0…

C语言王国探险记之数据类型

王国探险记系列 文章目录&#xff08;1&#xff09; 目录 1&#xff0c;写程序和数据类型有啥关系&#xff1f; 2.C语言里面都有啥数据类型&#xff1f; 1&#xff0c;数据类型的介绍 2&#xff0c;为什么浮点数描述的是小数 3&#xff0c;区分程序中的数字那些是整型&am…

02_深入浅出vite(二)--vue3全家桶+ts构建后管系统

安装基础包 npm create vitelatest # 这里选择的是VueTypescript的组合 cd vue-admin npm install# 先安装基础包 npm install vue-router4 npm i pinia npm i axios npm install sass --save-dev npm install element-plus --save npm install element-plus/icons-vue npm in…

SQL Server数据库 -- 表的基础查询

文章目录 一、单表查询基本结构二、单表查询结构语法 select聚合函数where模糊查询order bygroup byhaving三、多表查询基本结构四、多表查询结构语法 内连接自连接外连接五、总结 前言 学习了数据库&#xff0c;在以后公司等地方&#xff0c;你可能不会用到创建数据库或者表格…

LeetCode 双周赛 107(2023/06/24)滑动窗口与离散化

本文已收录到 AndroidFamily&#xff0c;技术和职场问题&#xff0c;请关注公众号 [彭旭锐] 和 [BaguTree Pro] 知识星球提问。 往期回顾&#xff1a;LeetCode 单周赛第 348 场 数位 DP 模版学会了吗&#xff1f; T1. 最大字符串配对数目&#xff08;Easy&#xff09; 标签&…

FlutterUnit 已上架 iOS,暗色模式全面支持

theme: cyanosis 一、FlutterUnit 的全平台支持 FlutterUnit 是我的一个开源项目&#xff0c;基于 Flutter 构建的一个 全平台 应用程序。现在很荣幸地宣布: FlutterUnit 已经上架 iOS 的 App Store &#xff0c;自此主流的几大平台均已提供体验。 项目地址: https://github.co…

Chrome Edge Firefox Safari 如何清除 DNS 缓存

Chrome Edge Firefox Safari 如何清除 DNS 缓存 如何清除浏览器的 DNS 缓存 (Chrome, Firefox, Safari) Chrome Chromium Edge Firefox Safari clear DNS Cache, flush DNS cache 请访问原文链接&#xff1a;https://sysin.org/blog/clear-browser-dns-cache/&#xff0c;查…

前端Vue仿京东天猫商品属性选择器自定义单选按钮

前端Vue仿京东天猫商品属性选择器自定义单选按钮&#xff0c; 下载完整代码请访问uni-app插件市场地址&#xff1a;https://ext.dcloud.net.cn/plugin?id13176 效果图如下&#xff1a; # cc-radioBtnBox #### 使用方法 使用方法 <!-- attrArr&#xff1a;属性数据 clic…

改进YOLOv5/YOLOv8:复现结合即插即用 | 高效多尺度注意力(EMA),模块成为YOLOv5改进的小帮手

高效多尺度注意力(EMA) 论文介绍简介EMA模块图像分类实验目标检测实验yolov5加入方法yolo注册yaml文件3563.pdf](https://arxiv.org/ftp/arxiv/papers/2305/2305.13563.pdf) 论文介绍 通道或空间的显著有效性 注意机制对产生更多可辨识的 特征表示的显著效果,在各种计算机视…