当前位置: 首页 > news >正文

AQS源码解析

关于 AQS,网上已经有无数的文章阐述 AQS 的使用及其源码,所以多这么一篇文章也没啥所谓,还能总结一下研究过的源码。源码解析和某某的使用,大概是互联网上 Java 文章中写得最多的主题了。

AQS

AQS 是 AbstractQueuedSynchronizer 的缩写,中文翻译过来就是抽象队列同步器。ReentrantLockReentrantReadWriteLockSemaphoreCountDownLatch 都是基于 AQS。AQS 的核心思想是,当线程请求获取资源时,如果资源空闲,则会将当前线程设置为资源的独占线程,成功获得锁;否则将获取锁失败的线程加入到排队队列中(CLH),并提供线程阻塞和线程唤醒机制。CLH 是一个虚拟的双向队列。

首先看一下 AQS 的关键属性。

// java.util.concurrent.locks.AbstractQueuedSynchronizer
private transient volatile Node head; //队列的头节点
private transient volatile Node tail; //队列的尾节点
private volatile int state; //同步状态

state 用于实现锁的可重入性。

  • 0 为表示没有线程持有该锁
  • 当存在线程获取锁成功,则 state 变为 1。如果是同一线程重复获得,则 state++
  • 如果存在线程释放锁,则 state–

上面的三个属性都存在对应的以 CAS 方式进行修改的方法,state 对应的是 compareAndSetState() 方法, head 对应的是 compareAndSetHead() 方法,tail 对应的是 compareAndSetTail()。以 CAS 的方式修改值能避免锁的竞争。

因为请求获取锁的线程会以 Node 节点的方式在 CLH 队列中排队,在分析 AQS 机制时也会大量涉及到 Node 节点,所以很有必要对 Node 节点进行分析。

CLH 队列中 Node 节点。

// java.util.concurrent.locks.AbstractQueuedSynchronizer.Node
volatile int waitStatus;  //当前节点在队列中的状态
volatile Node prev;  //前驱节点
volatile Node next;  //后继节点
volatile Thread thread;  //在该节点中排队的线程
Node nextWaiter;  //下一个处于condition或共享状态的节点//等待锁的两种状态
static final Node SHARED = new Node();  //共享
static final Node EXCLUSIVE = null; //独占//waitStatus的几个值 
static final int CANCELLED =  1;  //已取消
static final int SIGNAL    = -1;  //后继节点的线程需要唤醒
static final int CONDITION = -2;  //节点处于等待队列中
static final int PROPAGATE = -3; //线程处在SHARED情况下使用该字段

ReentrantLock

说是研究 AQS 源码,但 AQS 毕竟是一个抽象类,只实现了部分方法,另外一些方法会在子类中实现。所以我们也同样会涉及到 ReentrantLock 源码的研究。

ReentrantLock 的通常使用方式是:

class X {    private final ReentrantLock lock = new ReentrantLock();       public void m() {      lock.lock();       try {        // ... method body      } finally {        lock.unlock()      }    }  
}

ReentrantLock 存在两种锁:公平锁(FairSync)和非公平锁(NonfairSync),默认为非公平锁。使用公平锁时,线程会直接进入队列中排队,只有队列中第一个线程才能获取锁;使用非公平锁时,线程会先尝试获取锁,成功则占用锁,失败则在队列排队。对于 AQS 来说,公平锁和非公平锁的绝大部分方法都是共用的。

ReentrantLock 的主要方法有两个,一是使用 lock() 方法加锁,二是使用 unlock() 方法解锁。

加锁

非公平锁

我们首先来分析非公平锁的加锁过程。

// java.util.concurrent.locks.ReentrantLock.NonfairSync
final void lock() {  if (compareAndSetState(0, 1))  //通过cas方式设置同步状态setExclusiveOwnerThread(Thread.currentThread());  //成功,设置为独占线程else        acquire(1);  //失败,获取锁
}

lock() 方法中,会先尝试通过 CAS 的方式去获取锁,成功则设置为独占线程,否则执行 acquire() 方法。

// java.util.concurrent.locks.AbstractQueuedSynchronizer
public final void acquire(int arg) {  if (!tryAcquire(arg) &&  //尝试获取锁acquireQueued(addWaiter(Node.EXCLUSIVE), arg))  // 失败,则添加到等待队列selfInterrupt();  
}

acquire() 方法中,会再次尝试去获取锁,如果失败则加入到排队队列。

// java.util.concurrent.locks.ReentrantLock.NonfairSync#tryAcquire
protected final boolean tryAcquire(int acquires) {  return nonfairTryAcquire(acquires);  
}// java.util.concurrent.locks.ReentrantLock.Sync#nonfairTryAcquire
final boolean nonfairTryAcquire(int acquires) {  final Thread current = Thread.currentThread();  int c = getState();  if (c == 0) {  //如果当前没有线程持有锁if (compareAndSetState(0, acquires)) {  //state++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);   //state++,实现锁的可重入性return true;   //获得锁}  return false;  //如果已有线程持有锁,且当前线程不为独占线程,则获取锁失败
}

nonfairTryAcquire() 方法中,如果资源空闲,则再次尝试通过 CAS 的方式去获取锁。如果当前资源已被当前线程占用,则将 state++,以实现锁的可重入性。

// java.util.concurrent.locks.AbstractQueuedSynchronizer#addWaiter
// 设置队列尾节点
private Node addWaiter(Node mode) {  Node node = new Node(Thread.currentThread(), mode);   //新建排队节点 Node pred = tail;  //pred指向尾节点if (pred != null) {  //如果Pred指针是Null(说明等待队列中没有元素),或者当前Pred指针和Tail指向的位置不同(说明被别的线程已经修改)node.prev = pred;  //将新建节点的prev指向predif (compareAndSetTail(pred, node)) {  // 设置新建节点为尾节点pred.next = node;  return node;  }  }  enq(node);  return node;  
}//java.util.concurrent.locks.AbstractQueuedSynchronizer#enq
private Node enq(final Node node) {  for (;;) {  Node t = tail;  if (t == null) {   //如果尾节点为空,则说明队列还未初始化,if (compareAndSetHead(new Node()))  //初始化一个头节点tail = head;  } else {  //如果已经初始化,则设置为尾节点node.prev = t;  if (compareAndSetTail(t, node)) {  t.next = node;  return t;  }  }  }  
}

如果尝试多次获取锁都失败,则在 addWaiter() 方法中会将线程放到节点中,并设置为排队队列的尾节点。

//java.util.concurrent.locks.AbstractQueuedSynchronizer#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)) {  //如果前驱节点为头节点,则尝试获取锁setHead(node);  //获取锁成功,设置当前节点为头节点p.next = null; // help GC  failed = false;  return interrupted;  }  //来到这里,说明前驱节点不是头节点或者获取锁失败。判断当前节点是否要被阻塞if (shouldParkAfterFailedAcquire(p, node) &&  parkAndCheckInterrupt())  interrupted = true;  //线程中断成功}  } finally {  if (failed)  cancelAcquire(node);  }  
}

加入到排队队列中后,会在 acquireQueued() 方法中循环等待资源的获取,并判断线程是否需要被阻塞,直到线程获取成功或者抛出异常。

//java.util.concurrent.locks.AbstractQueuedSynchronizer#shouldParkAfterFailedAcquire
//靠前驱节点判断当前线程是否应该被阻塞
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {  //获取前驱节点的状态int ws = pred.waitStatus;  if (ws == Node.SIGNAL)  //如果前驱节点处于唤醒状态  return true;  if (ws > 0) {  //前驱节点处于取消状态      do {  //向前查找取消的节点,并将其从队列中删除node.prev = pred = pred.prev;  } while (pred.waitStatus > 0);  pred.next = node;  } else {  compareAndSetWaitStatus(pred, ws, Node.SIGNAL);  //设置前驱节点为唤醒状态}  return false;  
}
//java.util.concurrent.locks.AbstractQueuedSynchronizer#parkAndCheckInterrupt
//挂起当前线程,阻塞调用栈,返回当前线程的中断状态
private final boolean parkAndCheckInterrupt() {  LockSupport.park(this);  return Thread.interrupted();  
}

整一个非公平锁的加锁流程可以用如下流程图表示:

成功
失败
失败
队列中的线程去获取锁
true
false
设置当前节点为头节点
如果当前节点的前驱节点为头节点且获取锁成功
返回是否被中断过
当前节点是否需要被阻塞
阻塞当前线程
将当前节点添加到队列
不为空
为空
设置当前节点为尾节点
尾节点是否为空
返回当前节点
enq
为空
不为空
尾节点是否为空
初始化一个空的头节点
设置当前节点为尾节点
返回当前节点
lock
cas获取锁
设置为独占线程
尝试获取锁

公平锁

公平锁和非公平锁的流程中只有 lock() 方法和 tryAcquire() 方法存在差别。

//java.util.concurrent.locks.ReentrantLock.FairSync#lock
final void lock() {  acquire(1);  //直接获取锁
}

公平锁中会直接调用 acquire() 方法。

// java.util.concurrent.locks.ReentrantLock.FairSync#tryAcquire
protected final boolean tryAcquire(int acquires) {  final Thread current = Thread.currentThread();  int c = getState();  if (c == 0) {  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;  
}  

相对于 nonfairnonfairTryAcquire 方法,在没有线程持有锁时,增加了 hasQueuedPredecessors() 方法的判断,该方法用于判断队列中是否存在线程比当前线程等待时间更长。

// java.util.concurrent.locks.AbstractQueuedSynchronizer#hasQueuedPredecessors
public final boolean hasQueuedPredecessors() {     Node t = tail; Node h = head;  Node s;  return h != t &&  ((s = h.next) == null || s.thread != Thread.currentThread());  
}

分析一下这段代码:

  • 如果 h != t 为 true,说明队列正在初始化,或者已经初始化完成。
    • h != t 前提下,如果 (s = h.next) == null 为 true,说明队列正在初始化,因为在队列初始化过程中,是有可能存在 head 已经被初始化(不再等于 tail 了),但 head.next 还没有被设值为 node,这种情况下,因为队列中已经存在 Node,当前线程需要加到等待队列中,故返回 true。初始化过程在 AbstractQueuedSynchronizer#enq()
    • 但如果 (s = h.next) == null 为 false,说明队列中已经存在 Node,则判断该 Node 的线程是否与当前线程相同。如果 s.thread != Thread.currentThread() 为 true,说明不相同,需要进入等待队列。如果相同,说明当前线程可以获取锁。
  • 如果 h != t 为 false,说明队列为空,返回 false,说明可以去获得锁。

另外,s = h.next 这段代码获取的是 head 的下一个节点,因为 head 是虚节点,不存储数据,真正的数据存储在 head.next

解锁

相对于加锁过程,解锁过程比较简单,且公平锁和非公平锁共用同一个 lock() 方法。

// java.util.concurrent.locks.ReentrantLock#unlock
public void unlock() {  sync.release(1);  
}//java.util.concurrent.locks.AbstractQueuedSynchronizer#release
public final boolean release(int arg) {  if (tryRelease(arg)) {   //如果锁没有被任何线程持有Node h = head;  if (h != null && h.waitStatus != 0)  unparkSuccessor(h);  //解除后继节点的线程挂起return true;   }  return false;  
}

关于代码 h != null && h.waitStatus != 0,分为以下几种情况:

  1. h==null,说明头节点还未初始化。
  2. h!=null && h.waitStatus==0,说明后继节点还在运行中。因为如果节点已被取消,waitStatus 会被设置为 1;如果后继节点需要唤醒,waitStatus 会被设置为 -1;如果节点正在排队,waitStatus 则会设置为 -2。如果 waitStatus 为 0,说明已经处于运行中。
  3. h != null && h.waitStatus != 0 则会去唤醒后继节点。

release() 方法中,会先尝试去解锁,如果解锁成功且后继节点需要唤醒,则将后继节点取消挂起。

//java.util.concurrent.locks.ReentrantLock.Sync#tryRelease
//更新state状态,如果重入次数为0,则将锁的独占线程设置为null
protected final boolean tryRelease(int releases) {  //减少可重入次数int c = getState() - releases;  if (Thread.currentThread() != getExclusiveOwnerThread())  //如果当前线程不是该锁的独占线程,则抛出异常throw new IllegalMonitorStateException();  boolean free = false;  //该锁是否已被释放if (c == 0) {  //如果可重入次数已为0free = true;  setExclusiveOwnerThread(null);  //将锁的独占线程设置为null,表明不再有线程持有该锁}  setState(c);  //修改锁的状态return free;  
}

tryRelease() 方法中,会去减少锁的可重入次数,当可重入次数为 0 时,清空锁的独占线程。

// java.util.concurrent.locks.AbstractQueuedSynchronizer#unparkSuccessor
private void unparkSuccessor(Node node) {     int ws = node.waitStatus;  //获取头节点的waitStatusif (ws < 0)  //如果节点的waitStatus为负数,说明后继节点需要被唤醒或者正在排队compareAndSetWaitStatus(node, ws, 0);  //清除节点当前的waitStatus,设置为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)  //如果找到了,则将该节点取消挂起LockSupport.unpark(s.thread);  
}

unparkSuccessor() 方法中,会从尾节点开始从后往前遍历,找到队列中第一个没有被取消的节点,将该节点取消挂起。

整个解锁流程可以用如下流程图表示。

解除后继节点的挂起
清除该节点的waitStatus状态
如果头节点需要被唤醒
如果后继节点为null或者后继节点已被取消
从尾节点开始往前遍历找到第一个不被取消的节点
如果找到了不被取消的节点则取消该节点的挂起
尝试释放锁
该线程是否持有该锁
减少可重入次数
抛出异常
可重入次数是否为0
设置该锁的独占线程为null并释放掉锁
修改该锁的状态
如果释放锁成功且后继节点需要解除挂起

相关文章:

AQS源码解析

关于 AQS&#xff0c;网上已经有无数的文章阐述 AQS 的使用及其源码&#xff0c;所以多这么一篇文章也没啥所谓&#xff0c;还能总结一下研究过的源码。源码解析和某某的使用&#xff0c;大概是互联网上 Java 文章中写得最多的主题了。 AQS AQS 是 AbstractQueuedSynchronize…...

关于在VS2017中编译Qt项目遇到的问题

关于在VS2017中编译Qt项目遇到的问题 【QT】VS打开QT项目运行不成功 error MSB6006 “cmd.exe”已退出,代码为 2。如何在VS2017里部署的Qt Designer上编辑槽函数 【QT】VS打开QT项目运行不成功 error MSB6006 “cmd.exe”已退出,代码为 2。 链接 如何在VS2017里部署的Qt Design…...

Python web实战 | 使用 Flask 实现 Web Socket 聊天室

概要 今天我们学习如何使用 Python 实现 Web Socket&#xff0c;并实现一个实时聊天室的功能。本文的技术栈包括 Python、Flask、Socket.IO 和 HTML/CSS/JavaScript。 什么是 Web Socket&#xff1f; Web Socket 是一种在单个 TCP 连接上进行全双工通信的协议。它是 HTML5 中的…...

Android10 Recovery系列(一)隐藏recovery菜单项

一 、背景 起因是遇到了一个隐藏删除recovery菜单项的需求。在寻找解决问题的时候,我经历了找到源码位置,调试修改,生效,思考是否可拓展,优化修改,符合要求的整个过程,下面简单分享一下。如果不想立即实现效果或者只想看解决方案,可以直接看总结那一个部分 二 、准备…...

选好NAS网络储存解决方案,是安全储存的关键

随着网络信息的发展&#xff0c;NAS也越来越受到企业的关注&#xff0c;NAS网络存储除了提供简单的存储服务外&#xff0c;还可以提供更好的数据安全性、更方便的文件共享方式。但市面上的产品种类繁多&#xff0c;我们该如何选择合适的产品&#xff0c;通过企业云盘&#xff0…...

AnimateDiff论文解读-基于Stable Diffusion文生图模型生成动画

文章目录 1. 摘要2. 引言3. 算法3.1 Preliminaries3.2. Personalized Animation3.3 Motion Modeling Module 4. 实验5.限制6. 结论 论文&#xff1a; 《AnimateDiff: Animate Your Personalized Text-to-Image Diffusion Models without Specific Tuning》 github: https://g…...

centos7安装tomcat

安装tomcat 必须依赖 JDK 环境&#xff0c;一定要提前装好JDK保证可以使用 一、下载安装包 到官网下载 上传到linux 服务器 二、安装tomcat 创建tomcat 文件夹 mkdir -p /usr/local/tomcat设置文件夹权限 chmod 757 tomcat将安装包上传至 新建文件夹 解压安装包 tar zx…...

【C#教程】零基础从入门到精通

今天给大家分享一套零基础从入门到精通&#xff1a;.NetCore/C#视频教程&#xff1b;这是2022年最新整理的、590G的开发教程资料。课程涵盖了.Net各方面的知识&#xff0c;跟着这个教程学习&#xff0c;就足够了。 课程分类 1、C#从基础到精通教程&#xff1b; 2、Winform从…...

opencv rtsp 硬件解码

讨论使用opencv的reader 硬件解码的方案有太多种&#xff0c;如果使用ffmpeg硬件解码是最方便的&#xff0c;不方便的是把解码过后的GPU 拉到 CPU 上&#xff0c;再使用opencv的Mat 从cpu 上上载到gpu上&#xff0c;是不是多了两个过程&#xff0c;应该是直接从GPU mat 直接去…...

机器学习-Gradient Descent

机器学习(Gradient Descent) videopptblog 梯度下降(Gradient Descent) optimization problem: 损失函数最小化 假设本模型有两个参数&#x1d703;1和&#x1d703;2&#xff0c;随机取得初始值 求解偏微分&#xff0c;梯度下降对参数进行更新 Visualize: 确定梯度方向&…...

MySql003——SQL(结构化查询语言)基础知识

一、数据库的相关概念 DB&#xff1a;数据库&#xff08;Database&#xff09; 即存储数据的“仓库”&#xff0c;其本质是一个文件系统。它保存了一系列有组织的数据。DBMS&#xff1a;数据库管理系统&#xff08;Database Management System&#xff09; 是一种操纵和管理数…...

springCloud Eureka注册中心配置详解

1、创建一个springBoot项目 2、在springBoot项目中添加SpringCloud依赖 <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>2021.0.3</version><type>…...

gti 远程操作

目录 一. 分布式版本控制管理系统 1. 理解分布式版本控制管理系统 二. 创建远程仓库 ​编辑 ​编辑 三. 克隆远程仓库_HTTP 四. 克隆远程仓库_SSH 配置公钥 添加公钥 五. git 向远程仓库推送 六. 拉取远程仓库 七. 忽略特殊文件 八. 配置别名 一. 分布式版本控制管理…...

Ftrace

一、概述 Ftrace有剖析器和跟踪器。剖析器提供统计摘要&#xff0c;如激素胡和直方图&#xff1b;而跟踪器提供每一个事件的细节。 Ftrace剖析器列表&#xff1a; 剖析器描述function内核函数统计分析kprobe profiler启用的kprobe计数器uprobe profiler启用的uprobe计数器hi…...

Tomcat修改端口号

网上的教程都比较老&#xff0c;今天用tomcat9.0记录一下 conf文件夹下server.xml文件 刚开始改了打红叉的地方&#xff0c;发现没用&#xff0c;改了上面那行...

vue2企业级项目(一)

vue2企业级项目&#xff08;一&#xff09; 创建项目&#xff0c;并创建项目编译规范 1、node 版本 由于是vue2项目&#xff0c;所以 node 版本比较低。使用 12.18.3 左右即可 2、安装vue 安装指定版本的vue2 npm i -g vue2.7.10 npm i -g vue/cli4.4.63、编辑器规范 vsc…...

【前端知识】React 基础巩固(三十八)——log、thunk、applyMiddleware中间件的核心代码

React 基础巩固(三十八)——log、thunk、applyMiddleware中间件的核心代码 一、打印日志-中间件核心代码 利用Monkey Patching&#xff0c;修改原有的程序逻辑&#xff0c;在调用dispatch的过程中&#xff0c;通过dispatchAndLog实现日志打印功能 // 打印日志-中间件核心代码…...

hive删除数据进行恢复

在实际开发或生产中&#xff0c;hive表如果被误删&#xff0c;如被truncate或是分区表的分区被误删了&#xff0c;只要在回收站的清空周期内&#xff0c;是可以恢复数据的&#xff0c;步骤如下&#xff1a; &#xff08;1&#xff09; 先找到被删除数据的存放目录&#xff0c;…...

二、前端高德地图、渲染标记(Marker)引入自定义icon,手动设置zoom

要实现这个效果&#xff0c;我们先看一下目前的页面展示&#xff1a; 左边有一个图例&#xff0c;我们可以方法缩小地图&#xff0c;右边是动态的marker标记&#xff0c;到时候肯定时候是后端将对应的颜色标识、文字展示、坐标点给咱们返回、我们肯定可以拿到一个list&#xf…...

UDF和UDAF、UDTF的区别

UDF UDF&#xff08;User-defined functions&#xff09;用户自定义函数&#xff0c;简单说就是输入一行输出一行的自定义算子。 是大多数 SQL 环境的关键特性&#xff0c;用于扩展系统的内置功能。&#xff08;一对一&#xff09; UDAF UDAF&#xff08;User Defined Aggregat…...

小研究 - 浅析 JVM 中 GC 回收算法与垃圾收集器

本文主要介绍了JVM虚拟机中非常重要的两个部分&#xff0c;GC 回收算法和垃圾收集器。从可回收对象的标记开始&#xff0c;详细介绍 了四个主流的GC算法&#xff0c;详细总结了各自的算法思路及优缺点&#xff0c; 提出了何种情况下应该通常选用哪种算法。 目录 1 标记可回收对…...

Flowable-服务-骆驼任务

目录 定义图形标记XML内容Flowable与Camel集成使用示例设计Came路由代码 定义 Camel 任务不是 BPMN 2.0 规范定义的官方任务&#xff0c;在 Flowable 中&#xff0c;Camel 任务是作为一种特殊的服务 任务来实现的。主要做路由工作的。 图形标记 由于 Camel 任务不是 BPMN 2.…...

用html+javascript打造公文一键排版系统9:主送机关排版

一、主送机关的规定 公文一般在标题和正文之间还有主送机关&#xff0c;相关规定为&#xff1a; 主送机关 编排于标题下空一行位置&#xff0c;居左顶格&#xff0c;回行时仍顶格&#xff0c;最后一个机关名称后标全角冒号。如主送机关名称过多导致公文首页不能显示正文时&…...

SpringBoot 集成 EasyExcel 3.x 优雅实现 Excel 导入导出

介绍 EasyExcel 是一个基于 Java 的、快速、简洁、解决大文件内存溢出的 Excel 处理工具。它能让你在不用考虑性能、内存的等因素的情况下&#xff0c;快速完成 Excel 的读、写等功能。 EasyExcel文档地址&#xff1a; https://easyexcel.opensource.alibaba.com/ 快速开始 …...

RT1052 的四定时器

文章目录 1 Quad Timer&#xff0c;简称&#xff1a;QTMR2 单个通道的框图3 QTMR配置3.1 QTMR1 时钟使能。3.2 初始化 QTMR1。3.2.1 QTMR_Init 3.3 设置 QTMR1 通道 0 的定时周期。3.3.1QTMR_SetTimerPeriod 3.4 使能 QTMR1 通道 0 的比较中断。3.4.1 QTMR_EnableInterrupts 3.…...

ViT-vision transformer

ViT-vision transformer 介绍 Transformer最早是在NLP领域提出的&#xff0c;受此启发&#xff0c;Google将其用于图像&#xff0c;并对分类流程作尽量少的修改。 起源&#xff1a;从机器翻译的角度来看&#xff0c;一个句子想要翻译好&#xff0c;必须考虑上下文的信息&…...

Election of the King 2023牛客暑期多校训练营4-F

登录—专业IT笔试面试备考平台_牛客网 题目大意&#xff1a;有一个n个数的数组a&#xff0c;有n-1轮操作&#xff0c;每轮由每个数选择一个和它的差最大的数&#xff0c;如果相同就选值更大的&#xff0c;被最多数组选择的数字被删去&#xff0c;有相同的也去掉数值更大的那个…...

Nacos的搭建及服务调用

文章目录 一、搭建Nacos服务1、Nacos2、安装Nacos3、Docker安装Nacos 二、OpenFeign和Dubbo远程调用Nacos的服务1、搭建SpringCloudAlibaba的开发环境1.1 构建微服务聚合父工程1.2 创建子模块cloud-provider-payment80011.3 创建子模块cloud-consumer-order80 2、远程服务调用O…...

uniapp小程序自定义loding,通过状态管理配置全局使用

一、在项目中创建loding组件 在uniapp的components文件夹下创建loding组件&#xff0c;如图&#xff1a; 示例代码&#xff1a; <template><view class"loginLoading"><image src"../../static/loading.gif" class"loading-img&q…...

leetcode 45. 跳跃游戏 II

2023.7.30 class Solution { public:int jump(vector<int>& nums) {int step 0;int cover 0;int largest 0;if(nums.size() 1) return step;for(int i0; i<nums.size(); i){cover max(cover , inums[i]); //最大覆盖范围if(cover > nums.size()-1) retur…...