AQS抽象同步队列核心原理

CLH自旋锁

JUC中显式锁基于AQS抽象队列同步器,而AQS是CLH锁的一个变种。队列头结点可以获得锁,其他节点排队等候。

在这里插入图片描述

在争夺锁激烈的情况下,为了减少CAS空自旋(CAS需要CPU进行内部通信保证缓存一致性造成流量过大引起总线风暴),Java轻量级锁会升级为重量级锁,那么JUC基于CAS实现的轻量级锁如何避免总线风暴呢?答案是:使用队列对抢锁线性排队,最大程度上减少CAS操作数量。

CLH锁其实就是一种是基于队列(具体为单向链表)排队的自旋锁,申请加锁的线程首先会通过CAS操作在单向链表的尾部增加一个节点,之后该线程只需要在其前驱节点上进行普通自旋 ,等待前驱节点释放锁即可。由于CLH锁只有在节点入队时进行一下CAS的操作,在节点在加入队列之后,抢锁线程不需要进行CAS自旋,只需普通自旋即可。因此,在争用激烈的场景下, CLH锁能大大减少的CAS操作的数量,以避免CPU的总线风暴。

面试回答:抢锁线程在队列尾部加入一个节点,然后仅在前驱节点上做普通自旋,它不断轮询前一个节点状态,如果发现前一个节点释放锁,当前节点抢锁成功。

CLH 加锁过程

首先明确,CLH是指向前节点的单链表,每个节点Node包括至少三个参数:前向指针、locked 状态变量、线程引用。 当一个线程加入抢锁队列时,创建新Node,然后通过CAS加入到CLH队列的尾部,前向指针指向前一个节点,尾指针指向新加入的节点。然后,这个节点的线程会对前向的Node进行普通自旋,循环判断前驱节点的locked属性是否为false,如果为false就表示前驱节点释放了锁 ,当前线程抢锁成功。此线程抢到锁后locked一直为true,直到释放锁为false。

在这里插入图片描述

注意:以上普通自旋与CAS的区别是 while 循环中是否有 Thread.yield(); 让出CPU时间片,CAS是没有yield的。另外还有一个尾指针,tail属性使用AtomicReference类型是为了使得多个线程并发操作tail时不会发生线程安全问题。 locked 为 true 表示此线程自旋等待中或者正在执行临界区代码,下一个节点需要等待,直到释放了锁 locked 为false。

//CAS自旋:将当前节点插入到队列的尾部
while (!tail.compareAndSet(preNode, curNode)){preNode = tail.get();
}// 普通自旋,监听前驱节点的locked变量,直到其值为false
// 若前继节点的locked状态为true,则表示前一个线程还在抢占或者占有锁
while (curNode.getPrevNode().isLocked()){//让出CPU时间片,提高性能Thread.yield();
}

CLH 释放锁的过程

当一个线程执行完临界区代码后释放锁,首先将前向指针指向null,然后将locked设置为false。此时前面的节点没有引用,将会被GC。此时它后面的节点捕获了前面的locked为false立即抢占锁执行临界区代码。
在这里插入图片描述

AQS抽象同步器核心原理

为什么需要AQS?

  • CAS 恶性空循环浪费大量CPU资源
  • SMP架构CPU会导致总线风暴

在独占锁中,竞争资源在一个时间点只能被一个线程锁访问;队列的队首节点(队列的头部)表示占有锁的节点,新加入的抢锁线程则需要等待,会插入到队列的尾部。AQS是JUC提供的一个用于构建锁和同步容器的基础类。 AQS是CLH队列的一个变种,主要原理和CLH队列差不多。AQS队列内部维护的是一个FIFO的双向链表,这种结构的特点是每个数据结构都有两个指针,分别指向直接的前驱节点和直接的后驱节点。每个节点其实是由线程封装的,当线程争抢锁失败后会封装成Node加入到AQS队列中去;当获取锁的线程释放锁以后,会从队列中唤醒一个阻塞的节点。

在这里插入图片描述

AQS核心成员

状态标志位 state

volatile 修饰的 int state ,任何线程都可以回去state的最新值,通过getState() 和 setState() 设置同步状态值。一般通过CAS设置state值。调用的是Unsafe的compareAndSwapInt()方法实现CAS。以ReentrantLock为例,初始时 state = 0,表示未锁定状态。当一个线程调用tryAcquire() 独占该锁并将state+1,此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其他线程才有机会获取该锁。当然,释放锁之前, A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证state是能回到零态。

Node节点状态 waitStatus

在Node节点中定义了waitStatus来表示节点的状态,

  • cancelled,取消的,一般中断的或者超时的线程是这个状态,需要从等待队列中删除节点,此状态不参与竞争。
  • signal,表示后驱节点处于等待状态,一旦此节点释放锁或者被取消了,通知后驱节点运行。
  • condition,表示该节点处在条件队列中阻塞,表示此节点处于等待队列中,当调用了condition的singal方法才会从等待队列转移到同步队列中去竞争锁。
  • propagate,共享锁的延续,当自己的状态是这个就通知后续节点也共享状态获取锁。
  • 为0,表示当前节点处于初始状态。

**注意:**有人不知道等待队列和同步队列,等待队列就是阻塞了的线程,例如调用wait()方法就进入到了等待队列。只有在同步队列中的线程才有资格竞争锁,处于等待队列中的线程如果被 notify 或者 signal 唤醒进入到同步队列,并不是被唤醒了就立马能获得锁运行!同步队列简单讲就是已经具备运行条件,只差一把锁的资源,抢到了立马就运行。而等待队列中的线程没有抢锁资格。

注意,以上两个都是状态,但是state是全局唯一的,标识的是AQS队列的锁状态,而waitStatus是每个节点都有的内部属性。

thread成员

Node 的thread用来存放线程的引用,next指针指向后续节点。

抢占式常量标识
  • shared,表示线程因为获取共享资源时阻塞而添加到队列中。
  • exclusive,表示线程因为获取独占资源阻塞而被添加到队列中。

双向同步队列

AQS的内部队列是CLH队列的变种,每当线程通过AQS获取锁失败时,线程将被封装成一个Node节点,通过CAS原子操作插入队列尾部。当有线程释放锁时, AQS会尝试让队首的后驱节点占用锁。

在这里插入图片描述

AQS是一个同步器实现了所得基本功能,JUC的的显式锁如ReentrantLock、 ReentrantReadWriteLock,线程同步工具如Semaphore,内部都使用了AQS作为等待队列。

AQS抢占锁的原理

新线程来了,使用AQS的Acquire尝试获取锁,如果失败了则构造独占式节点Node,通过CAS方式自旋地将节点加入到同步队列队尾。构造Node时需要设置 thread 为当前线程。当节点进入到了队列后,本来应该自旋地判断前置节点是否为头节点并尝试获取锁。但是为了不浪费资源,如果前置节点不是头节点则自旋过程中会阻塞线程。而只有它的前驱节点成为头节点后才会唤醒后置线程尝试获取锁。自旋是节点中的线程自己完成的。AQS不像CLH节点那样做空旋转浪费资源,而是会被挂起park进入阻塞状态。如果头节点获取了锁,那么此线程会停止自旋而去执行临界区的代码。

其实判断前置节点还需要判断节点的状态,例如只有前驱节点状态为signal,则它的后续节点进行自我阻塞。一开始一个线程加入到队尾waitStatu肯定是0表示初始状态,此时会进行CAS自旋获取锁。在自旋中检测是否挂起,找到自己的有效前驱(指的是不是取消的节点,一般为初始状态0或者共享状态)然后将其设置为signal,之后自己马上进入阻塞。可以理解为当前面的节点成为头节点后记得唤醒我。 如果前面的节点为取消状态的节点,就继续往前找,并建立唤醒关系。只有是signal状态才会唤醒后续节点。

在这里插入图片描述

释放锁的过程,当头节点释放锁后就唤醒后面的节点,后面的节点成为新的头结点。如果遇见某个节点是无效节点则直接删除,也就是说无效节点的出队操作是在唤醒后驱节点的线程之后。

ReentrantLock 如何借助AQS实现的?

ReentrantLock把所有Lock接口的操作都委派到一个Sync类上,该类继承了AQS。NonfairSync为非公平(或者不公平)同步器, FairSync为公平同步器。这两个同步器都继承Sync,然后Sync继承AQS。ReentrantLock的lock()和unlock()调用的其实是Sync的lock()和release()方法。ReentrantLock的显式锁操作是委托(或委派)给一个Sync内部类的实例完成的。而Sync内部类只是AQS的一个子类,所以本质上ReentrantLock的显式锁操作是委托(或委派)给AQS完成的。

ReentrantLock AQS 抢锁原理

公平锁与非公平锁区别:

  • 加锁区别:**加锁时是否判断前面有节点在排队。**公平锁是先判断是否有节点在排队,如果有则加入到队列后面CAS方式。非公平的是新节点来了直接去抢锁,两次抢锁失败了才加入到队列中,并且阻塞。
  • 解锁区别:如果新来了一个线程Thread-4,此时直接抢占锁,如下图1所示;如果没有新来的线程抢占,则按照队列的顺序公平地唤醒头结点后面的节点并持有锁。如下图图2所示。

img

img

非公平式抢锁 (同步器是NonfairSync)

首先用一个CAS操作,判断state是否是0(表示当前锁未被占用),如果是0就调用AQS的acquire方法以CAS方式把它置为1,并且设置当前线程为该锁的独占线程,表示获取锁成功。当多个线程同时尝试占用同一个锁时, CAS操作只能保证一个线程操作成功,剩下的只能乖乖去排队。如果发现当前线程和独占锁的线程是同一个就重入,state++;如果两次抢锁都失败了,那么就老实入队

ReentrantLock“非公平”性即体现在这里:如果占用锁的线程刚释放锁, state置为0,而排队等待锁的线程还未唤醒,新来的线程就直接抢占了该锁,那么就“插队”了。

非公平同步器ReentrantLock.NonfairSync的核心思想就是当前进程尝试获取锁的时候,如果发现锁的状态位是0,就直接尝试将锁拿过来,然后执行setExclusiveOwnerThread(),根本不管同步队列中的排队节点。

公平式抢锁 (同步器是FairSync)

首先获取锁的状态,如果state为0则判断头节点是否有后驱节点,如果有后驱节点就立即去排队。否则CAS将state设置为1进行抢占,抢占成功后将当前线程设置为锁占有线程。

AQS条件队列

Condition与Object的wait()/notify()作用是相似的,都是使得一个线程等待某个条件( Condition),只有当该条件具备signal()或者signalAll()方法被调用时等待线程才会被唤醒,从而重新争夺锁。不同的是, Object的wait()/notify()由JVM底层实现,而Condition接口与实现类完全使用Java代码实现。通过Condition的await()和signal()方法进行线程间的阻塞与唤醒。

一个Condition对象是一个单条件的等待队列 :

在这里插入图片描述

在一个显式锁上,我们可以创建多个等待任务队列,这点和内置锁不同, Java内置锁上只有唯一的一个等待队列。

await()等待方法原理

当线程调用await()方法时,说明当前线程的节点为当前AQS队列的队首节点,正好处于占有锁的状态, await()方法需要把该线程从AQS队列挪到Condition等待队列里。然后执行while循环,将该节点的线程阻塞,直到该节点离开等待队列,重新回到同步队列成为同步节点后,线程才退出while循环。

在这里插入图片描述

signal()唤醒方法原理

上调用signal()方法后,等待队列中的firstWaiter会被加入到同步队列中,等待节点被唤醒。

在这里插入图片描述

提一嘴

AQS 是一个同步容器与队列锁的模板类,用户可以通过AQS模板类自定义自己的锁和同步队列。只需要实现核心的获取锁、释放锁的排队和出队过程。分为模板方法和构造方法,钩子方法用户的子类自己实现。

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

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

相关文章

马上医疗项目介绍

“马上好医”项目白皮书 一、大型医疗挂号微服务“马上好医”医疗项目 “马上好医”即为网上医疗预约挂号系统,首先,由于互联网的发展,衍生出非常多的便民医疗服务的需求,而网上预约挂号则是其中一个便民需求,我们能…

基于Javaweb实现ATM机系统开发实战(十五)退卡和转账跳转实现

首先创建一个servlet接受和处理请求: package com.atm.servlet;import javax.servlet.*; import javax.servlet.http.*; import javax.servlet.annotation.*; import java.io.IOException;//用户退出 WebServlet("/logout") public class ExitServlet ex…

JavaScript逻辑运算符

not运算符(!) 反着来and运算符(&&) 全true才trueor运算符(||) 全false才false 举例 const hasDriversLicense true; const Drinking false;console.log(hasDriversLicense && Drinking…

7. Spring Boot 配置文件

目录 1. 配置文件作用 2. 配置文件格式 3. properties 配置文件说明 3.1 properties 基本语法 3.2 读取配置文件 3.3 缺点 4. yml 配置文件说明 4.1 properties 基本语法 4.2 读取配置文件 4.3 yml 配置不同的数据类型 布尔值 整数值 null 值 配置对象 配置集合 …

SpringBoot集成kafka全面实战

本文是SpringBootKafka的实战讲解,如果对kafka的架构原理还不了解的读者,建议先看一下《大白话kafka架构原理》、《秒懂kafka HA(高可用)》两篇文章。 一、生产者实践 普通生产者 带回调的生产者 自定义分区器 kafka事务提交…

第三大的数

414、第三大的数 class Solution {public int thirdMax(int[] nums) {Arrays.sort(nums);int tempnums[0];int ansnums[0];int count 0;// if(nums.length<3){// return nums[nums.length-1];// }// else {for(int inums.length-1;i>0;i--){if (nums[i]>nums[i…

安防监控视频汇聚平台EasyCVR修改录像计划等待时间较长是什么原因?

安防监控视频EasyCVR视频融合汇聚平台基于云边端智能协同&#xff0c;支持海量视频的轻量化接入与汇聚、转码与处理、全网智能分发等。音视频流媒体视频平台EasyCVR拓展性强&#xff0c;视频能力丰富&#xff0c;具体可实现视频监控直播、视频轮播、视频录像、云存储、回放与检…

UI设计工具都有哪些好用的推荐?

对于UI设计的初学者来说&#xff0c;掌握一个实用且易于使用的界面UI软件是非常重要的。今天&#xff0c;我整理了四个易于使用的界面UI软件。让我们看看。 即时设计 即时设计是一款免费的在线 UI 设计工具&#xff0c;无系统限制&#xff0c;浏览器打开即可使用&#xff0c;…

如何使用 After Effects 导出摄像机跟踪数据到 3ds Max

推荐&#xff1a; NSDT场景编辑器助你快速搭建可二次开发的3D应用场景 在本教程中&#xff0c;我将展示如何在After Effects中跟踪实景场景&#xff0c;然后将相机数据导出到3ds Max。 1. 项目设置 步骤 1 打开“后效”。 打开后效果 步骤 2 转到合成>新合成以创建新合…

Python - Opencv应用实例之树叶自动分割、标签及统计分析系统

Python - Opencv应用实例之树叶自动分割、标签及统计分析系统 本文通过Python+opencv 实现这样的需求:输出位置和角度(x, y, r),并标记出轮廓基于传统图像处理算法实现,算法原理:输入图像 -> 灰度化 -> 二值化 -> 形态学处理 -> 轮廓提取 -> 树叶中心定位 -…

window10脚本转服务教程

先说下脚本/我们启动的一些三方服务转window本机服务目前我了解到的好处 一键设置开机自启、随用随启、延时自启解决一些服务类应用启动后会阻塞当前dos窗口导致桌面一直要开着的问题脚本化服务注册&#xff0c;方便管理&#xff0c;统一运维… 1. 实践涉及内容介绍 编写好的…

YOLOV8最强操作教程.

YoloV8详细训练教程. 相信各位都知道yolov8发布了&#xff0c;也是U神大作&#xff0c;而且V8还会出论文喔&#xff01; 2023.1.17 更新 yolov8-grad-cam热力图可视化链接 2023.1.20 更新 YOLOV8改进-添加EIoU,SIoU,AlphaIoU,FocalEIoU 链接 2023.1.30 更新 如果你需要修改或者…

最新Ai创作源码ChatGPT商用运营源码/支持GPT4.0+支持ai绘画+支持Mind思维导图生成

本系统使用Nestjs和Vue3框架技术&#xff0c;持续集成AI能力到本系统&#xff01; 支持GPT3模型、GPT4模型Midjourney专业绘画&#xff08;全自定义调参&#xff09;、Midjourney以图生图、Dall-E2绘画Mind思维导图生成应用工作台&#xff08;Prompt&#xff09;AI绘画广场自定…

基于Spring包扫描工具和MybatisPlus逆向工程组件的数据表自动同步机制

公司产品产出的项目较多。同步数据库表结构工作很麻烦。一个alter语句要跑到N个客户机上执行脚本。超级费时麻烦。介于此&#xff0c;原有方案是把增量脚本放到一resource包下&#xff0c;项目启动时执行逐行执行一次。但由于模块开发人员较多&#xff0c;总有那么一两个机灵鬼…

高电压放大器ATA-2021B技术指标

随着ATA-2021H高压放大器的升级改版&#xff0c;新品ATA-2021B高电压放大器走进了更多工程师、研究人员的视野。相比于升级之前&#xff0c;ATA-2021B高压放大器拥有了更多更好地优势&#xff0c;可以更好地的帮助研究人员高效完成测试项目。今天Aigtek小编就带大家了解一下关于…

windos 服务器设置指定ip访问指定端口,其他ip不能访问

需求&#xff1a;设置指定ip访问指定端口&#xff0c;其他ip不能访问 一&#xff0c;禁止所有ip访问 需要打开IP安全策略 或者winR 输入secpol.msc 1.先创建一个ip安全策略 2.点击添加&#xff0c;不使用添加向导&#xff0c;建一个安全策略 继续点添加 二&#xff0c;放开需…

苍穹外卖 Spring Task 来单提醒 催单Apache ECharts day10~11

苍穹外卖-day10 课程内容 Spring Task订单状态定时处理WebSocket来单提醒客户催单 功能实现&#xff1a;订单状态定时处理、来单提醒和客户催单 订单状态定时处理&#xff1a; 来单提醒&#xff1a; 客户催单&#xff1a; 1. Spring Task 1.1 介绍 Spring Task 是Spring框架提供…

【chatGpt】关于websocket连接中对未授权的捕捉问题

目录 问题 有效提问 有效的细节提问 问题 一路上&#xff0c;通过简单的error进行判断弹出授权&#xff0c;会有很多乱弹的现象&#xff1a; &#xff08;1&#xff09;链路正常切换会断 &#xff08;2&#xff09;服务器没有启动会连接不上 &#xff08;3&#xff09;没…

Python pygame(GUI编程)模块最完整教程(7)

上一篇文章&#xff1a; Python pygame(GUI编程)模块最完整教程&#xff08;6&#xff09;_Python-ZZY的博客-CSDN博客 总目录&#xff1a; README.md Python-ZZY/Python-Pygame最完整教程 - Gitee.com 21 OpenGL与Pygame 不会OpenGL的读者可以跳过本章节。 21.1 OpenGL简…

CRM系统化整合从N-1做减法实践 | 京东物流技术团队

1 背景 京销易系统已经接入大网、KA以及云仓三个条线商机&#xff0c;每个条线商机规则差异比较大&#xff0c;当前现状是独立实现三套系统分别做支撑。 2 目标 2022年下半年CRM目标是完成9个新条线业务接入&#xff0c;完成销售过程线上化&#xff0c;实现销售规则统一。 …