详解ReentrantLock--三种加锁方式
目录
介绍AQS:
直观方式解释加锁的流程:
Node是什么:它里面有什么属性呢
图解队列的排队过程:
源码分析三种加锁流程:
我们先讲解一下非公平锁的加锁流程:
Lock()方式加锁:
在源码里对于Lock()的解释:
源码详解枷锁的过程:
进入lock()方法后显示
进入sync.lock();方法:
展示非公平锁的实现lock():
展示公平锁实现的lock()
产看方法acquire(1);
查看方法tryAcquire(int arg):
非公平锁实现:
公平锁实现:
查看创建Node的方法addWaiter(Node.EXCLUSIVE)
查看方法acquireQueued();
查看方法shouldParkAfterFailedAcquire()
注:Node()状态
tryLock()方式加锁:
tryLock()方式:
tryLock(long time, TimeUnit unit)方式
tryAcquireNanos()方法
doAcquireNanos():
cancelAcquire()
lockInterruptibly()方式
acquireInterruptibly()方法
doAcquireInterruptibly()方法:
在我们打开源码时,可以看一下这个源码的作者:
/** @since 1.5* @author Doug Lea*/
Doug Lea 是 Java 并发编程领域的权威人物,他在并发编程方面的贡献尤为突出。Doug Lea 是 SUNY Oswego 的计算机科学教授,并且是纽约先进技术中心软件工程实验室的联合主任。他的工作对 Java 并发编程产生了深远的影响。
Doug Lea 撰写了《JAVA并发编程实践》一书,这本书是 Java 领域的经典之作,全面深入地探讨了 Java 平台上的多线程和并发编程技术。这本书不仅帮助开发者理解和掌握如何在 Java 环境中编写高效、安全且可维护的并发代码,还提供了许多标准设计技术,用于解决常见的并发编程挑战。
Doug Lea 在 Java 并发编程中的另一个重要贡献是开发了 java.util.concurrent 包,这个包在 JDK 5.0 中被整合,极大地丰富了 Java 的并发编程工具。这个包包括了线程池、并发集合、原子变量和锁等工具,极大地简化了并发编程的复杂性。
介绍AQS:
全程为AbstractQueuedSynchronizer
AQS中文被称为队列同步器
AQS是一个抽象的接口。他是JUC包下的基类。JUC包下的很多功能都是基于AQS实现的。
AQS里面维护了一个由volitile修饰的state变量和内置的FIFO队列完成线程的排队和加锁操作。
volitile维护的state保证了可见性和有序性,在设置state是,里面提供了compareAndSetState()方法。CAS方式保证了原子性
直观方式解释加锁的流程:
为大家图解什么是FIFO队列,队列里的数据类型是什么。是如何连接的
-
FIFO队列是双向链表实现的。
-
里面每个节点的数据类型是Node
-
特殊的是,队列的头节点是一个伪节点,这个节点的线程信息是null
Node是什么:它里面有什么属性呢
Node是AQS的内部类。
-
为大家展示Node的结构:
-
-
里面记录着状态,前置的指针,后置的指针,还记录了队列的头节点和尾节点
图解队列的排队过程:
这个图片只是为了展现这个队列的状态,对于流程我们并没直接的展现,但是需要注意的是伪节点的Thread是null.
源码分析三种加锁流程:
我们先讲解一下非公平锁的加锁流程:
-
如果线程A执行CAS将state修改为1,则线程1得到所资源,执行业务代码。
-
这时线程B在去获取所资源是发现state不是0,并且自己并不是有锁的线程,那么B将自己封装为Node,进入双向队列。
-
但是在进入队列是,头节点必须是一个伪节点,若一开始没有伪节点,那就创建之后,将这个节点挂在伪节点后,将前置节点的状态修改为-1 之后就可以挂起线程
我们在创建ReentrantLock()时都是借助多态创建.在此为大家展示一下lock的API
Lock()方式加锁:
在源码里对于Lock()的解释:
Acquires the lock. If the lock is not available then the current thread becomes disabled for thread scheduling purposes and lies dormant until the lock has been acquired. Implementation Considerations A Lock implementation may be able to detect erroneous use of the lock, such as an invocation that would cause deadlock, and may throw an (unchecked) exception in such circumstances. The circumstances and the exception type must be documented by that Lock implementation. 获取锁。 如果锁不可用,则当前线程将出于线程调度目的而被禁用,并在获取锁之前处于休眠状态。 实施注意事项 实现 Lock 可能能够检测到对锁的错误使用,例如会导致死锁的调用,并可能在这种情况下引发(未经检查的)异常。该实现必须记录 Lock 情况和异常类型。
源码详解枷锁的过程:
进入lock()方法后显示
public void lock() {sync.lock();}
进入sync.lock();方法:
abstract void lock();//发现是抽象方法。
展示非公平锁的实现lock():
final void lock() {//首先进入之后直接使用CAS方式修改,如果成功,将持有锁的线程设置为当前线程,返回if (compareAndSetState(0, 1))setExclusiveOwnerThread(Thread.currentThread());else //失败进入acquire方法acquire(1);}
展示公平锁实现的lock()
final void lock() {//发现公平锁下的方式就是 acquire(1); acquire(1); }
产看方法acquire(1);
public final void acquire(int arg) {//尝试获取线程,如果为获取if (!tryAcquire(arg) &&//开始方法acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//addWaiter(Node.EXCLUSIVE)开始封装Node对象 Node.EXCLUSIVE这个指的是现在的锁的类型时互斥锁//封装后讲数据直接开始放置到队列里面acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//将自己终端,就是在队列里面排队,等待唤醒selfInterrupt();}
查看方法tryAcquire(int arg):
protected boolean tryAcquire(int arg) {throw new UnsupportedOperationException(); }
非公平锁实现:
protected final boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires); }进入方法:nonfairTryAcquire(acquires)
final boolean nonfairTryAcquire(int acquires) {//设置线程为当前线程final Thread current = Thread.currentThread();//获取stateint c = getState();//c==0 没有线程持有锁,抢一把if (c == 0) {//CAS方式枪锁成功,设置一下当前线程,直接返回trueif (compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}//state不是0,但是线程时但该案当前线程,锁重入 state+1 直接返回获取锁成功else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;//放置state超过int最大值if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");//设置state的值setState(nextc);return true;}//未成功返回false 返回的是flase之后开始将这个线程放入到FIFO队列里return false; }
公平锁实现:
protected final boolean tryAcquire(int acquires) {//获取当前线程final Thread current = Thread.currentThread();//获取state的值int c = getState();//如果是0的话,这里展现的是公平锁的君子风范:if (c == 0) {//如果里面没有Node或者是头节点之后没有Node等待,或者自己就是头节点之后的点那就可以尝试拿锁if (!hasQueuedPredecessors() &&//如果成功 返回truecompareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}//state不是0,但是线程时但该案当前线程,锁重入 state+1 直接返回获取锁成功else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;//放置state超过int最大值if (nextc < 0)throw new Error("Maximum lock count exceeded");//设置state的值setState(nextc);return true;}//未成功返回false;返回的是flase之后开始将这个线程放入到FIFO队列里return false;}//就是判断公平锁是否能够干活 public final boolean hasQueuedPredecessors() {// The correctness of this depends on head being initialized// before tail and on head.next being accurate if the current// thread is first in queue.Node t = tail; // Read fields in reverse initialization orderNode h = head;Node s;return h != t &&((s = h.next) == null || s.thread != Thread.currentThread()); }
查看创建Node的方法addWaiter(Node.EXCLUSIVE)
/** *就是简单的创建一个node */ private Node addWaiter(Node mode) {Node node = new Node(Thread.currentThread(), mode);// Try the fast path of enq; backup to full enq on failureNode pred = tail;if (pred != null) {node.prev = pred;if (compareAndSetTail(pred, node)) {pred.next = node;return node;}}enq(node);return node;}
查看方法acquireQueued();
//对于lock()方法里面的中断操作我们先忽略。 final boolean acquireQueued(final Node node, int arg) {//设置状态,当前未获取到锁资源boolean failed = true;try {boolean interrupted = false;//死循环,必须完成一件事 for (;;) {//查找当前节点的前驱节点final Node p = node.predecessor();//开始判断,如果我的前置节点就是伪节点(注意是伪节点)直接在词使用tryAcquire()尝试获取锁资源if (p == head && tryAcquire(arg)) {//如果成功,就设置一些头节点setHead(node);p.next = null; // help GC//设置已经获取锁资源failed = false;//返回的值是false.return interrupted;}//如果前置节点不是伪节点的话,if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())interrupted = true;}} finally {if (failed)cancelAcquire(node);}}
查看方法shouldParkAfterFailedAcquire()
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {int ws = pred.waitStatus;//如果等于-1 证明前置节点可以唤醒当前节点 if (ws == Node.SIGNAL) return true;if (ws > 0) {//如果是>0的话就是当前前置节点无效,使用循环找到有效的节点,地将当前节点挂在有效节点的后面do {node.prev = pred = pred.prev;} while (pred.waitStatus > 0);pred.next = node;} else {//那就是-2,-3使用ACS将状态设置为-1compareAndSetWaitStatus(pred, ws, Node.SIGNAL);}return false;}
注:Node()状态
/** waitStatus value to indicate thread has cancelled */static final int CANCELLED = 1;/** waitStatus value to indicate successor's thread needs unparking */static final int SIGNAL = -1;/** waitStatus value to indicate thread is waiting on condition */static final int CONDITION = -2;/*** waitStatus value to indicate the next acquireShared should* unconditionally propagate*/static final int PROPAGATE = -3;
tryLock()方式加锁:
tryLock()方式:
public boolean tryLock() {//这就是非公平锁的TryAcquire()方法return sync.nonfairTryAcquire(1); }final boolean nonfairTryAcquire(int acquires) {//设置线程为当前线程final Thread current = Thread.currentThread();//获取stateint c = getState();//c==0 没有线程持有锁,抢一把if (c == 0) {//CAS方式枪锁成功,设置一下当前线程,直接返回trueif (compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}//state不是0,但是线程时但该案当前线程,锁重入 state+1 直接返回获取锁成功else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;//放置state超过int最大值if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");//设置state的值setState(nextc);return true;}//未成功返回false 返回的是flase之后开始将这个线程放入到FIFO队列里return false; }
tryLock(long time, TimeUnit unit)方式
public boolean tryLock(long timeout, TimeUnit unit)throws InterruptedException {return sync.tryAcquireNanos(1, unit.toNanos(timeout)); }
tryAcquireNanos()方法
public final boolean tryAcquireNanos(int arg, long nanosTimeout)throws InterruptedException {//如果线程中断,抛出异常if (Thread.interrupted())throw new InterruptedException();return tryAcquire(arg) //在抢一手,失败的话执行下面的方法||doAcquireNanos(arg, nanosTimeout);}
doAcquireNanos():
private boolean doAcquireNanos(int arg, long nanosTimeout)throws InterruptedException {//如果时间结束,直接返回失败if (nanosTimeout <= 0L)return false;//设置结束时间final long deadline = System.nanoTime() + nanosTimeout;//将当前节点设置出来final Node node = addWaiter(Node.EXCLUSIVE);//设置时候失败获取锁boolean failed = true;try {for (;;) { //这是死循环,我们注意查看代码的出口//获取当前节点的前置节点final Node p = node.predecessor();//如果前置节点是伪节点,尝试拿锁if (p == head && tryAcquire(arg)) {setHead(node);p.next = null; // help GCfailed = false;//成功直接返回return true;}//计算现在还剩多长时间nanosTimeout = deadline - System.nanoTime();//时间结束,直接返回falseif (nanosTimeout <= 0L)return false;//时间还是有的,修改前置节点的状态,准备挂起if (shouldParkAfterFailedAcquire(p, node) &&//时间有,但是太少了,连挂起的时间都不足,也是结束nanosTimeout > spinForTimeoutThreshold)//如果时间足够,开始将节点挂起 继续死循环等待结束条件LockSupport.parkNanos(this, nanosTimeout);//如果当前线程被其他线程中断,直接甩出异常if (Thread.interrupted())throw new InterruptedException();}} finally {//甩出异常也是结束死循环,但是需要将节点取消if (failed)cancelAcquire(node);}}
cancelAcquire()
private void cancelAcquire(Node node) {//如果是null 直接返回if (node == null)return;//将当前节点的线程设置为nullnode.thread = null;//找到前置线程Node pred = node.prev;//找到状态正常的前置节点while (pred.waitStatus > 0)node.prev = pred = pred.prev;// 将前置线程的next设置为当前线程的nextNode predNext = pred.next;// 设置状态为1,代表节点取消node.waitStatus = Node.CANCELLED;// 如果是尾节点的话,直接就是简单的将前置节点设知为尾节点if (node == tail && compareAndSetTail(node, pred)) {//设置前置节点的next为空compareAndSetNext(pred, predNext, null);} else {//当前的节点不是尾节点或者是CAS操作失败//那就是;两种情况,一种是前置节点就是伪节点 第二种是前置接点不是伪节点int ws;if (pred != head //前置节点不是伪节点&&((ws = pred.waitStatus) == Node.SIGNAL //前置节点状态是否为-1 || //不是-1的话,或着是其他的<0的状态,设置为-1 这样的话可以将线程唤醒(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&//并且前置的线程不是nullpred.thread != null) {Node next = node.next;//判断当前线程有后继节点,将后继节点关在前置节点上if (next != null && next.waitStatus <= 0)compareAndSetNext(pred, predNext, next);} else {//当前节点的前置节点是伪节点 直接将线程唤醒 这是释放锁的操作,在后续的章节开始讲解unparkSuccessor(node);}node.next = node; // help GC} }
lockInterruptibly()方式
public void lockInterruptibly() throws InterruptedException {sync.acquireInterruptibly(1); }
acquireInterruptibly()方法
public final void acquireInterruptibly(int arg)throws InterruptedException {//判断线程是否终端if (Thread.interrupted())throw new InterruptedException();//如果尝试获取所资源失败if (!tryAcquire(arg))//进入方法doAcquireInterruptibly(arg); }
doAcquireInterruptibly()方法:
private void doAcquireInterruptibly(int arg)throws InterruptedException {final Node node = addWaiter(Node.EXCLUSIVE);//当前获取资源失败boolean failed = true;try {for (;;) //注意出口条件//获取当前节点的前置节点final Node p = node.predecessor();//如果前置节点是伪节点,尝试拿锁if (p == head && tryAcquire(arg)) {setHead(node);p.next = null; // help GCfailed = false;//成功直接返回return true;}//获取锁资源失败 开始挂起资源,如果挂起之后被中断的方式挂起,抛出异常if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())throw new InterruptedException();}} finally {//因为异常也是出口条件,将节点取消if (failed)cancelAcquire(node);//还是取消节点的方法,同上} }
相关文章:

详解ReentrantLock--三种加锁方式
目录 介绍AQS: 直观方式解释加锁的流程: Node是什么:它里面有什么属性呢 图解队列的排队过程: 源码分析三种加锁流程: 我们先讲解一下非公平锁的加锁流程: Lock()方式加锁: 在源码里对于Lock()的解…...

SQL 基础语法(一)
文章目录 1. SQL 分类2. 数据库操作3. 数据表操作4. 增删改操作5. 查询操作6. 用户管理7. 权限控制 1. SQL 分类 2. 数据库操作 #创建数据库 create database if not exists test;#查询所有数据库 show databases;#查询当前数据库 select database();#删除数据库 drop databas…...

Python酷库之旅-第三方库Pandas(190)
目录 一、用法精讲 881、pandas.Index.is_方法 881-1、语法 881-2、参数 881-3、功能 881-4、返回值 881-5、说明 881-6、用法 881-6-1、数据准备 881-6-2、代码示例 881-6-3、结果输出 882、pandas.Index.min方法 882-1、语法 882-2、参数 882-3、功能 882-4、…...

Spring学习笔记_19——@PostConstruct @PreDestroy
PostConstruct && PreDestroy 1. 介绍 PostConstruct注解与PreDestroy注解都是JSR250规范中提供的注解。 PostConstruct注解标注的方法可以在创建Bean后在为属性赋值后,初始化Bean之前执行。 PreDestroy注解标注的方法可以在Bean销毁之前执行。 2. 依赖…...

《云计算网络技术与应用》实训8-1:OpenvSwitch简单配置练习
1.按《云计算网络技术与应用》实训5-1进行环境配置,安装好OVS 2.开启OVS虚拟交换机 3.创建一个网桥br0 4.查看网桥列表 5.把ens34网卡连接到网桥br0上 6. 查看网桥br0所有端口 7.列出网卡ens34连接的所有网桥列表 8.查看OVS网络状态 9.将网桥br0上连接的网卡ens34删…...

【架构艺术】服务架构稳定性的基础保障
一个产品随着不断研发,其服务架构的复杂度会越来越高。随着产品的用户体量变大,为了保证产品能够长线运营,就需要保证整个服务架构的稳定性。因此,今天这篇文章,就从实操的角度,粗浅讨论一下,服…...

Python中使用pip换源的详细指南
在Python开发过程中,我们经常需要安装各种第三方库。pip是Python的包管理工具,用于安装和管理Python库。然而,由于网络原因,有时访问默认的Python包索引(PyPI)可能会比较慢。这时,我们可以通过更…...

一站打包国际智慧教育自主学练软件资源
👑🌟一站打包国际智慧教育自主学练软件与资源平台,欧美学校正在使用,不出国就可以学👒🎈 💛 多元学练:我们正在使用的自主学练软件是美国学校一线教师使用的,涵盖了英语…...

用股票API获取高频行情数据来实现数据分析和量化
用股市API获取高频行情来实现数据分析和量化 使用股市API是一种有效的方式来获取高频行情数据,以便进行行情数据分析和量化交易。Python是一种广泛应用于金融数据领域的编程语言,它提供了丰富的库和工具,可用于与股市API进行交互。通过调用股…...

C++ | Leetcode C++题解之第526题优美的排列
题目: 题解: class Solution { public:int countArrangement(int n) {vector<int> f(1 << n);f[0] 1;for (int mask 1; mask < (1 << n); mask) {int num __builtin_popcount(mask);for (int i 0; i < n; i) {if (mask &am…...

【RabbitMQ】01-RabbitMQ
1. MQ MQ可以有更好的并发性。 2. 安装 docker run \-e RABBITMQ_DEFAULT_USERitheima \-e RABBITMQ_DEFAULT_PASS123321 \-v mq-plugins:/plugins \--name mq \--hostname mq \-p 15672:15672 \-p 5672:5672 \--network hm-net\-d \rabbitmq:3.8-management3. 结构 4. 数据…...

使用 ADB 在某个特定时间点点击 Android 设备上的某个按钮
前提条件 安装 ADB:确保你已经在计算机上安装了 Android SDK(或单独的 ADB)。并将其添加到系统环境变量中,以便你可以在命令行中运行 adb。 USB调试:确保 Android 设备已启用 USB 调试模式。这可以在设备的“设置” -…...

【随笔】对于开发者而言,你对什么事情感到失落?亦或者你上一次感到有成就感是什么时候?你遇到过怎样格局的老板?
这是博主的一篇随笔文章,一起和大家聊聊工作上的一些事和一些感受,我觉得我们这个群体,同样有很多优秀的、幽默的人。只不过有些表达和沟通并不是我们擅长的,包括博主也是,这是我们的劣势和缺点。没关系,这…...

【LeetCode】两数之和返回两数下标、数组形式整数相加
主页:HABUO🍁主页:HABUO 1.两数之和返回两数下标 题目:给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。 你可以假设每种输…...

Kubernetes中的secrets存储
华子目录 2.secrets2.1secrets功能介绍2.2secrets的创建2.2.1从文件创建2.2.2编写yaml文件 2.3secret的使用案例2.3.1将secret挂载到volume中2.3.2设置子目录映射secret密钥2.3.3将secret设置为环境变量2.3.4存储docker register的认证信息spec.imagePullSecrets[] 2.secrets …...

使用 Elastic、OpenLLMetry 和 OpenTelemetry 跟踪 LangChain 应用程序
作者:来自 Elastic Bahubali Shetti Langchain 应用程序的使用正在增长。构建基于 RAG 的应用程序、简单的 AI 助手等的能力正在成为常态。观察这些应用程序更加困难。考虑到现有的各种选项,本博客展示了如何将 OpenTelemetry 检测与 OpenLLMetry 结合使…...

【论文复现】VALL-E:语音合成的新里程
📕作者简介:热爱跑步的恒川,致力于C/C、Java、Python等多编程语言,热爱跑步,喜爱音乐、摄影的一位博主。 📗本文收录于论文复现系列,大家有兴趣的可以看一看。 📘相关专栏C语言初阶、…...

java项目之微服务在线教育系统设计与实现(springcloud)
风定落花生,歌声逐流水,大家好我是风歌,混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的闲一品交易平台。项目源码以及部署相关请联系风歌,文末附上联系信息 。 项目简介: 微服务在线教育系统设计与…...

P3-1.【结构化程序设计】第一节——知识要点:算法、顺序结构程序设计、if语句的语法结构及各种用法
讲解视频: P3-1.【结构化程序设计】第一节——知识要点:算法、顺序结构程序设计、if语句的语法结构及各种用法 知识要点:算法、顺序结构程序设计、if语句的语法结构及各种用法 一、算法、顺序结构程序设计任务分析 知识要点:算法…...

Vue2——单页应用程序路由的使用
一.单页应用程序与多页应用程序之间的比较 二.单页的应用场景 系统类网站 / 内部网站 / 文档类网站 / 移动端网站 三.路由的介绍 1. 什么是路由 路由是一种映射关系 2. Vue中的路由是什么 路径和组件的映射关系 四.VueRouter的使用 5个基础步骤(固定) …...

变分法(Calculus of Variations)
变分法(Calculus of Variations)是数学的一个分支,主要研究函数的极值问题,即寻找一个函数,使得某个泛函达到最大值或最小值。泛函是将函数作为变量的函数,与通常的函数不同,泛函的变量是函数本…...

包括 Nginx、Gateway、Nacos、Dubbo、Sentinel、RocketMQ 和 Seata 的调用链路描述:
以下是一个更详细和清晰的客户端请求在 Spring Cloud Alibaba 框架中,包括 Nginx、Gateway、Nacos、Dubbo、Sentinel、RocketMQ 和 Seata 的调用链路描述: 1. 客户端请求 用户在浏览器或移动应用中发起请求(例如,获取用户信息的…...

【P2-1】ESP8266 WIFI模块STA、AP、STA+AP、TCP/UDP透传工作模式介绍与AT指令介绍
前言:本文对ESP8266 WIFI模块STA、AP、STA+AP、TCP/UDP透传工作模式进行介绍;以及AT指令介绍,包括基础AT指令,WIFI功能AT指令、TCP/IP相关AT指令、常用AT指令实例进行介绍。 ESP8266 WIFI模块的接线及固件烧写可参考我的这篇博客:正点原子ATK-ESP8266 WIFI模块接线及固件…...

《C#语法一篇通》,20万字,48小时阅读,持续完善中。。。
本文摘录了C#语法的主要内容,接近20万字。 所有鸡汤的味道都等于马尿! 如果你相信任何所谓的鸡汤文章,智商堪忧。 计算机语言没有”好不好“之说,骗子才会告诉你哪个语言好,学好任何一本基础语言(C&#…...

[node] 2 fs文件系统模块
前言 fs模块是Node.js官方提供的内置Api,用来操作文件的模块。它提供了一系列的属性和方法,来满足用户对文件的操作需求 目标 1 掌握fs中文件处理方法readFile、writeFile等的基础用法 2 node如何安装 3 一些常用的终端快捷键 #mermaid-svg-rPp2nDYrW33gLvuI {font-family:&q…...

【react】基础知识点学习
1. 创建项目 npm install -g create-react-app npx create-react-app my-app cd my-app npm startindex.js为入口文件,App.js为根组件。 如何将react应用挂载在页面上? 将App组件渲染到id为root的DOM元素中 2. JSX JSX是|avaScript和XML(HTML)的缩写…...

D4--哈夫曼树和不等式
看文先三连,养成好习惯~看文先三连,养成好习惯~看文先三连,养成好习惯~ 目录 知识点: 堆排序: 优先队列: 定义:(默认大顶堆) 入队: 出队: 取队顶&…...

详解RabbitMQ三种队列类型
RabbitMQ 是一个强大的消息队列系统,它提供了多种队列类型以满足不同的使用需求。本文将探讨三种主要队列类型:经典队列、仲裁队列和流式队列,并讨论它们的区别和选型建议。 经典队列(Classic Queues) 简介ÿ…...

openGauss数据库-头歌实验1-3 创建和管理模式
一、创建和使用模式 (一)任务描述 本关任务:基于 openGauss 学习创建模式的相关知识。 (二)相关知识 为了完成本关任务,你需要掌握:1.openGauss 的常用操作,2.SQL 创建模式相关语…...

森林火灾检测数据集(猫脸码客 第233期)
森林火灾检测数据集 森林火灾是一种具有巨大破坏性的自然灾害,每年在全球范围内造成巨大损失。为了有效应对森林火灾,及早发现和快速响应是至关重要的。传统上,森林火灾的检测主要依赖于人工巡逻和卫星遥感技术。然而,这些方法存…...