JUC05-AQS、ReentrantLock原理

news2025/8/4 4:20:16

一、AQS

全称是 AbstractQueuedSynchronizer,是同步器的相关框架,juc中很多锁的实现类依赖同步器(AQS的子类)完成核心操作

要点

  1. 内部维护state变量资源状态,state=0表示当前无线程占用,state!=0代表该锁正在被线程占用
  2. 提供FIFO的等待队列,类似于monitor的entryList
  3. 提供多个条件变量来做阻塞、唤醒操作,类似于monitor的waitList,但粒度更细,可以减少虚假唤醒的出现。

子类主要实现的方法

  • tryAcquire
  • tryRelease
  • tryAcquireShared
  • tryReleaseShared
  • isHeldExclusively

acquire(AQS已实现)

// 如果获取锁失败
if (!tryAcquire(arg)) {
 // 入队, 可以选择阻塞当前线程 park unpark
}

**release(AQS已实现)

// 如果释放锁成功
if (tryRelease(arg)) {
 // 让阻塞线程恢复运行
}

自定义同步器

 class MySync extends AbstractQueuedSynchronizer {
        @Override
        protected boolean tryAcquire(int arg) {
            if(compareAndSetState(0, 1)) {
                // 加上了锁,并设置 owner 为当前线程
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        @Override
        protected boolean tryRelease(int arg) {
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        @Override // 是否持有独占锁
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }

        public Condition newCondition() {
            return new ConditionObject();
        }
    }

二、ReentrantLock

在这里插入图片描述

重要组件

NonfairSync

 static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
        final void lock() {
        	//cas设置state成功,则设置当前线程为OwnerThread
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else //否则执行acquire进入阻塞队列
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

非公平锁实现原理

非公平性:虽然等待队列是先来先服务的,但是当锁被释放时,等待队列中的线程仍可能与队列外的线程去竞争锁,可能会竞争失败

注意:等待队列的head指向的要么是正在运行的线程所在节点,要么是dummy结点(不含线程)

非公平性具体体现在acquireQueued方法中

加锁

 public final void acquire(int arg) {
  		//1.尝试获取锁,获取成功则结束
  		//2.获取失败则构建Node,关联当前线程,将其添加到等待队列中
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    
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; // help GC
                    failed = false;
                    return interrupted;
                }
                //首次修改前驱节点的waitStatus=-1,返回false
                //再次执行时,waitStatus已经为-1,返回true,此时执行parkAndCheckInterrupt()中断当前线程
                //当线程恢复时从这开始执行,继续循环
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }    
   
private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
		//将node添加至队尾
        enq(node);
        return node;
    }

解锁

 public final boolean release(int arg) {
  		//1.尝试释放锁,释放成功则将state设置为0并返回true
        if (tryRelease(arg)) {
        	//2.找到头结点
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
    
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;
 }
   
         
private void unparkSuccessor(Node node) {
       
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

       //获取当前节点的后继节点
        Node s = node.next;
        //后继节点若为空或者后继节点的waitStatus>0则遍历队列直到遇到第一个waitStatus<=0的节点或者遍历到队尾
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
		//恢复s节点关联线程的运行
        if (s != null)
            LockSupport.unpark(s.thread);
    }

可重入原理

进行加锁时,首先判断state是否为0,若不为0则说明该锁已经被占用,判断占用锁的线程是否为当前线程,若是则将state+1,返回true,不是则返回false。同理释放锁的时候会对判断占用锁的线程是否为当前线程,若是则将state-1,state=0表示释放成功返回true,否则返回false

static final class NonfairSync extends Sync {
 // ...
 
 // Sync 继承过来的方法, 方便阅读, 放在此处
 final boolean nonfairTryAcquire(int acquires) {
	 final Thread current = Thread.currentThread();
	 int c = getState();
	 if (c == 0) {
		 if (compareAndSetState(0, acquires)) {
			 setExclusiveOwnerThread(current);
			 return true;
		 	}
	 }
	 // 如果已经获得了锁, 线程还是当前线程, 表示发生了锁重入
	 else if (current == getExclusiveOwnerThread()) {
	 	// state++
	 	int nextc = c + acquires;
		 if (nextc < 0) // overflow
		 	throw new Error("Maximum lock count exceeded");
		 setState(nextc);
		 return true;
	 }
	 return false;
 }
 
 // Sync 继承过来的方法, 方便阅读, 放在此处
 protected final boolean tryRelease(int releases) {
	 // state-- 
	 int c = getState() - releases;
	 if (Thread.currentThread() != getExclusiveOwnerThread())
	 	throw new IllegalMonitorStateException();
	 boolean free = false;
	 // 支持锁重入, 只有 state 减为 0, 才释放成功
	 if (c == 0) {
		 free = true;
		 setExclusiveOwnerThread(null);
	 }
	 setState(c);
 	 return free;
 }
}

可打断/不可打断模式

不可打断模式

当我们的线程被打断后会重新进入循环,如果当前线程位于等待队列的第一个则会尝试获取锁,获取失败则再次进入阻塞,若不是队列第一个则直接进入阻塞状态,代码见acquireQueued()

简单说就是不可打断模式下,若阻塞的线程被打断会再次进入阻塞队列

NoneFairSyn

private final boolean parkAndCheckInterrupt() {
		//1.将当前线程阻塞在此,若被打断才会往下执行
        LockSupport.park(this);
        //2.若被打断则返回true,同时清楚interrupted标记
        //若是被正常恢复则返回false
        return Thread.interrupted();
    }

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; // help GC
                    failed = false;
                    return interrupted;
                }
                //首次修改前驱节点的waitStatus=-1,返回false
                //再次执行时,waitStatus已经为-1,返回true,此时执行parkAndCheckInterrupt()中断当前线程
                //当线程恢复时从这开始执行,继续循环
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    //若被打断则将interrupted置为true
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
 }  
 public final void acquire(int arg) {
 	    //1.尝试获取锁,获取失败则加入阻塞队列
 	    //2.从阻塞队列出来后,返回interupted(该interrupted是方法内部的变量,不是线程的标记)
        if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            //若interrupted=true,则执行selfInterrupt()重新将线程的interrupted设为true
            selfInterrupt();
    }  

可打断模式

若阻塞的线程被打断则直接抛出异常,不继续在等待队列进行等待

public final void acquireInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        //1.尝试获取锁
        if (!tryAcquire(arg))
        	//2.获取失败则进入阻塞队列,但是是不可打断模式
        	//该方法与acquireQueued()基本一致,只是对于打断处理不同,如果被打断则抛出异常退出等待队列
            doAcquireInterruptibly(arg); 
    }

公平锁原理

公平锁采用的是FairSync,与NoneFairSync的区别主要在于tryAcquire的不同,在竞争锁时先要判断队列中是否存在线程等待,若不存在或者等待队列中第一个线程就是当前线程时获取成功,否则进入等待队列

FairSync

 protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                //1. 判断队列是否为空 ||  队列中第一个待恢复的线程是否为当前线程
                if (!hasQueuedPredecessors() &&
                    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;
        }
acquire与NoneFairSync一致,都使用的是AQS的方法

条件变量

每个条件变量都维护了一条等待队列,如图所示
在这里插入图片描述

await()

它将state置为0后park该线程并设置ws=-2,将该线程添加到此条件变量的等待队列中

public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            //创建node关联当前线程,并将node添加到等待队列队尾
            Node node = addConditionWaiter();
            //释放锁
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            while (!isOnSyncQueue(node)) {
            	//park当前线程
                LockSupport.park(this);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }

signal

从头遍历等待队列,直到遇见第一个ws=-2的节点,将该节点移出等待队列并添加到阻塞队列尾部

public final void signal() {
			//1.校验当前线程是否为持有锁的线程
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
             //2.得到头结点
            Node first = firstWaiter;
            if (first != null)
                //3.signal头节点
                doSignal(first);
        }

//将头节点移出等待队列
//如果头结点的waitStatus>0则一直移出头结点
//直到遇到ws<0的节点,将其放入阻塞队列
private void doSignal(Node first) {
            do {
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
                first.nextWaiter = null;
            } while (!transferForSignal(first) &&
                     (first = firstWaiter) != null);
        }

final boolean transferForSignal(Node node) {
        /*
         * If cannot change waitStatus, the node has been cancelled.
         */
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;

        /*
         * Splice onto queue and try to set waitStatus of predecessor to
         * indicate that thread is (probably) waiting. If cancelled or
         * attempt to set waitStatus fails, wake up to resync (in which
         * case the waitStatus can be transiently and harmlessly wrong).
         */
        Node p = enq(node);
        int ws = p.waitStatus;
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }

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

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

相关文章

Mongodb实验二——分片集群搭建

目录 前言 题目一 1.创建副本集实例&#xff08;Mongod&#xff09; cfg0_0配置 cfg0_1配置 cfg0_2配置 ch0_0配置 ch0_1配置 ch0_2配置 ch1_0配置 ch1_1配置 ch1_2配置 2.初始化副本集 ch0conf.js配置 ch1conf.js配置 3.创建路由节点&#xff08;分片管理服务器…

Live800:避开客服雷区,提升客服转化

小刘在某在线商城开了个小店铺&#xff0c;店铺做得不错&#xff0c;宝贝销量稳定&#xff0c;小日子过得有滋有味。 最近一段时间&#xff0c;流量持续下滑&#xff0c;销量跟着走低&#xff0c;可愁死了。 "流量有波动很正常&#xff0c;但是一直降一直降&#xff0c;肯…

《恋上数据结构与算法》第1季:算法概述

数据结构与算法的学习笔记目录&#xff1a;《恋上数据结构与算法》的学习笔记 目录索引算法概述1. 算法和数据结构1.1 什么是算法1.2 什么是数据结构2. 时间复杂度2.1 如何判断一个算法的好坏呢&#xff1f;2.2 基本操作执行次数2.3 大O表示法3. 空间复杂度3.1 概念定义4. 算法…

MCE | BCL6 小分子也能发挥类 PROTAC 的功能

图示摘要&#xff1a;来自作者 Jonas Koeppel (Ph. D., Department of Medical Oncology, Dana-Farber Cancer Institute) 的 Twitter杂合双功能降解技术 (PROTAC&#xff0c;靶蛋白配体-Linker-E3 连接酶配体组成的“三体”聚合物) 已被用于多种疾病相关靶点的降解剂开发&…

老年患者植入LVAD的挑战:胃肠道出血

EF值降低的心力衰竭随着年龄的增长越来越常见。据研究报道&#xff0c;75岁以上人群的患病率是普通人群的4倍&#xff0c;目前是老年人死亡和住院的最常见原因之一。治疗方案主要包括心脏移植(HT)和长期左心室辅助装置(LVAD)植入&#xff0c;这两种方法已被临床研究证明可降低患…

Flutter高仿微信-第34篇-单聊-小视频

Flutter高仿微信系列共59篇&#xff0c;从Flutter客户端、Kotlin客户端、Web服务器、数据库表结构、Xmpp即时通讯服务器、视频通话服务器、腾讯云服务器全面讲解。 详情请查看 效果图&#xff1a; 详情请参考 Flutter高仿微信-第29篇-单聊 &#xff0c; 这里只是提取小视频的部…

Android App开发即时通信中通过SocketIO在客户端与服务端间传输文本和图片的讲解及实战(超详细 附源码)

需要源码和服务端代码请点赞关注收藏后评论区留下QQ~~~ 一、通过SocketIO传输文本消息 虽然HTTP协议能够满足多数常见的接口交互&#xff0c;但是他属于短连接&#xff0c;每次调用完就自动断开连接&#xff0c;并且HTTP协议区分了服务端和客户端&#xff0c;双方的通信过程是…

机器学习知识经验分享之三:基于卷积神经网络的经典目标检测算法

文章目录前言一、一阶段目标检测算法1.YOLO系列算法2.SSD检测算法3. RetinaNet检测算法二、两阶段目标检测算法1.Faster R-CNN检测算法2.Mask R-CNN检测算法3.Cascade R-CNN检测算法总结前言 本系列文章将对机器学习知识进行分享总结。便于大家从理论层面了解人工智能基础原理…

软件被人后台篡改了收款码属于入侵吗?

最近很多做平台的小伙伴&#xff0c;碰到了同样的问题&#xff0c;就是软件程序后台被恶意篡改收款二维码 这个问题出现在平台主身上无疑是雪上加霜&#xff0c;第一时间找到了小蚁君&#xff0c;分析了一下当时的情况&#xff0c;先安装了小蚁的入侵检测系统&#xff0c;显示…

计算机毕业设计之java+ssm协同办公系统

项目介绍 本公司文档协同办公管理系统采用SSM&#xff08;SpringSpringMVCMyBatis&#xff09;框架开发,主要包括系统用户管理模块、用户信息模块、文件信息管理、个人事务管理、资料信息管理、登录模块、和退出模块等多个模块. 本系统主要包含了等系统用户管理、用户信息管理…

webpack5 PWA解决Web App 项目网络离线情况没法访问情况

为什么 开发 Web App 项目&#xff0c;项目一旦处于网络离线情况&#xff0c;就没法访问了。 我们希望给项目提供离线体验。 是什么 渐进式网络应用程序(progressive web application - PWA)&#xff1a;是一种可以提供类似于 native app(原生应用程序) 体验的 Web App 的技术。…

Go语言中操作Redis

Redis介绍 Redis是一个开源的内存数据库&#xff0c;Redis提供了多种不同类型的数据结构&#xff0c;很多业务场景下的问题都可以很自然地映射到这些数据结构上。 除此之外&#xff0c;通过复制、持久化和客户端分片等特性&#xff0c;我们可以很方便地将Redis扩展成一个能够包…

Word控件Spire.Doc 【图像形状】教程(5) 如何在 C# 中将文本环绕在图像周围

Spire.Doc for .NET是一款专门对 Word 文档进行操作的 .NET 类库。在于帮助开发人员无需安装 Microsoft Word情况下&#xff0c;轻松快捷高效地创建、编辑、转换和打印 Microsoft Word 文档。拥有近10年专业开发经验Spire系列办公文档开发工具&#xff0c;专注于创建、编辑、转…

frp篇---frp-notify + Gotify 实现 FRP 用户上线通知

frp-notify Gotify 实现 FRP 用户上线通知1. 安装frp_notify2. Gotify 配置3. frp-notify 配置启动 frp_notify开机自启动1. 安装frp_notify 一个专注于消息通知的 frp server manager plugin 实现&#xff0c;让你对进入 frps 的连接了如指掌&#xff0c;不再裸奔。 项目链…

SSM之Spring注解式缓存Redis

目录 Sprig整合Redis 导入相关pom依赖 添加对应的的配置文件 IEDA安装lombok插件 引入外部多文件 applicationContext.xml的整合配置文件 redis注解式缓存 Cacheable 测试类注解 Cacheable 的测试代码 CachePut CachePut测试代码 CacheEvict CacheEvict测试代码 Spr…

如何考察候选人 Vue 技术水平?

答对这些问题&#xff0c;检测你是否真正掌握了Vue 请说一下响应式数据的原理 默认 Vue 在初始化数据时&#xff0c;会给 data 中的属性使用 Object.defineProperty 重新定义所有属性&#xff0c;当页面到对应属性时&#xff0c;会进行依赖收集(收集当前组件中的 watcher)如果…

论文阅读【8】Conditional Random Fields: An Introduction

1.概述 1.1 论文相关 这篇论文是介绍一个经典模型&#xff0c;条件随机场&#xff08;CRF&#xff09;。在很多领域中都存在序列标注任务&#xff0c;例如生物信息识别&#xff0c;计算机语言学和语音识别任务&#xff0c;其中自然语言处理中的词性标注任何和命名实体识别任务…

JS 数据结构:链表

单链表 每个节点中只包含一个指针域的链表称为单链表。 头结点—其指针域指向表中第一个结点的指针&#xff08;头结点不是必须的&#xff0c;只是习惯上加上头结点&#xff0c;而头结点的数据域一般记录的是该链表的相关数据&#xff0c;如&#xff1a;链表长度&#xff09;…

Redis-Linux中安装Redis、命令操作Redis

目录 一、Redis简介 NoSQL与SQL的区别 二、Linux上安装redis 上传并解压redis.gz 进入 redis的解压目录&#xff0c;执行命令 make ​编辑 修改redis为守护进程 们测试一下能否远程连接RedisDesktopManager客户端 开放6379端口 授权&#xff0c;允许远程连接 三、redis命…

小程序上新(2022.10.13~11.14)

20221101 【官方公告】境外主体小程序补充信息存储地区通知20221103 小程序基础库 2.27.1 更新 新增 框架 新增 xr-fame 能力&#xff0c;kanata 更新 详情新增 组件 map 组件新增 bindrendersuccess 属性 详情 (官方文档还查不到这个)新增 API 新增 wx.getRendererUserAgen…