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

【多线程】线程池(上)

文章目录

  • 线程池基本概念
    • 线程池的优点
    • 线程池的特点
  • 创建线程池
    • 自定义线程池
    • 线程池的工作原理
    • 线程池源码分析
    • 内置线程池
      • `newFixedThreadPool`
      • `SingleThreadExecutor`
      • `newCachedThreadPool`
      • `ScheduledThreadPool`
  • 线程池的核心线程是否会被回收?
  • 拒绝策略
    • ThreadPoolExecutor.AbortPolicy
    • ThreadPoolExecutor.CallerRunsPolicy
    • ThreadPoolEcecutor.DiscardPolicy
    • ThreadPoolExecutor.DiscardOldestPolicy

线程池基本概念

在讲解线程池之前,我们应该先来了解池化技术

池化技术:
池化技术是一种用于管理和复用线程的技术,旨在提高系统的性能和资源利用率。线程池通过预先创建一组线程来处理任务,从而避免频繁地创建和销毁线程带来的开销

池化技术不仅在线程池中体现,还在数据库连接池,HTTP连接池,字符串常量池中均有体现.

如果不使用池化技术,我们创建一个线程的步骤:

  • 手动创建线程对象
  • 执行任务
  • 执行完毕,释放线程对象
    此处,每一次创建线程资源和释放线程资源,均会消耗系统资源,而如果我们采用线程池的方式,由线程池统一创建和管理线程资源,就可以降低系统资源的消耗,提高对资源的利用率.

所以线程池,提供一种限制和管理资源的方式.

线程池的优点

  • 降低资源消耗:通过重复利用已经创建的线程降低线程创建和销毁造成的资源消耗.
  • 提高程序的响应速度:当任务到达时,任务可以不需要等待线程的创建就可以立即执行
  • 便于统一管理线程对象:线程池可以统一进行线程分配
  • 可以控制最大的并发数(线程池会限制最大的线程对象的个数,这样可以限制并发量,避免无限制的创建线程资源,造成系统资源的损耗)

线程池的特点

  • 线程复用
  • 控制最大并发数
  • 管理线程

创建线程池

自定义线程池

对于线程池的管理和使用,我们使用在java.util.concurrent下的ThreadPoolExecutor线程池工具类
来进行线程池的创建管理

public class ThreadPoolExecutor extends AbstractExecutorService{//成员属性:private volatile int corePoolSize;//核心线程数private volatile int maximumPoolSize;//最大线程数private volatile long keepAliveTime;//非核心线程的最大存活时间private final BlockingQueue<Runnable> workQueue;//工作队列private volatile ThreadFactory threadFactory;//线程工厂private volatile RejectedExecutionHandler handler;//拒绝策略//...//线程池的构造方法:public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler)//...                           }       

参数:

  • corePoolSize:任务队列未达到队列最大容量时,最大可以同时运行的线程数量
  • maximumPoolSize:任务队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数
  • workQueue:新任务来的时候,会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,新任务就会被存放在队列中
  • keepAliveTime:当线程池中的线程数量大于corePoolSize,即有非核心线程(线程池中核心线程以外的线程)时,这些非核心线程空闲后不会立即被销毁,而是会等待,直到等待的时间超过了keepAliveTime才会被回收销毁
  • Unit:keepAliveTime参数的时间单位
  • threadFactory:executor创建新线程时会用到
  • headler:拒绝策略(后续详解))

接下来我们采用图解的方式,来系统讲解一下这里各种参数具体使用,并了解线程池的工作原理.

线程池的工作原理

我们下述线程池原理的讲解,以一个corePoolSize(核心线程数)为9,maximumPoolSize(最大线程数)为13,workQueue(工作队列)大小为5,keepAliveTime(非核心线程存活时间)为10s来举例.

  • 当此时的线程池中已被创建的线程<核心线程数,线程池会创建新的线程执行任务

  • 此时的线程池中已创建的线程池数==核心线程数
    在这里插入图片描述

  • 此时新的任务进入线程池中,会先进入阻塞队列中进行等待,核心线程中如果存在空闲的线程,就会去阻塞队列中取任务进行执行
    在这里插入图片描述
    在这里插入图片描述

  • 如果工作阻塞队列已满,核心线程也在执行任务,但最大线程数>已创建线程数,那么进入的任务后,线程池会创建新的线程,直到最大线程数==已创建线程数.
    在这里插入图片描述

  • 在线程池中工作阻塞队列已满,且已创建线程数==最大线程数时,此时再来任务时,线程池就会触发拒绝策略.
    在这里插入图片描述

  • 当线程池中的额外创建的线程空闲下来了,是否马上就会销毁呢?
    并不是,线程池会根据设定的keepAliveTime时间来判断什么时候销毁这些线程.
    那么是不是一定要销毁原本创建的额外创建的线程呢?
    并不是,线程池只需要维护核心线程数最大线程数的数量即可,所以线程池中的线程并没有一个明确的身份
    在这里插入图片描述
    其中的几个线程空闲时间超过了keepAliveTime时间后:
    在这里插入图片描述
    以上就是线程池的工作原理和参数的理解.

    线程池源码分析

    我们从execute入手,了解线程池的源码中是如何实现上述过程的.

    public void execute(Runnable command) {if (command == null)throw new NullPointerException();int c = ctl.get();if (workerCountOf(c) < corePoolSize) {if (addWorker(command, true))return;c = ctl.get();}if (isRunning(c) && workQueue.offer(command)) {int recheck = ctl.get();if (! isRunning(recheck) && remove(command))reject(command);else if (workerCountOf(recheck) == 0)addWorker(null, false);}else if (!addWorker(command, false))reject(command);}
    

    在这里插入图片描述
    接下来我们来观察一下addWorker方法里是如何创建一个新的线程的

private boolean addWorker(Runnable firstTask, boolean core) {
//......Worker w = null;try {w = new Worker(firstTask);final Thread t = w.thread;

在这里插入图片描述
我们发现,最终是调用了ThreadFactory参数来创建线程的.

这里还需要注意Worker类是实现了Runnable接口的类,所以Worker本身也是一个Runnable,所以创建线程是,调用的this.thread=getThreadFactory().newThread(this)传入的this对象就是Worker,所以线程执行的run()方法,是Worker内部的run()方法,而调用内部的run()方法后,在调用runWorker(this)方法,我们来看一看runWorker()方法中的内容:
在这里插入图片描述
我们看到,这里传入了参数this对象,其中this中的成员属性firstTask就是我们传入的FirstTask
在这里插入图片描述
在这里我们看到了task.run()的身影,说明此处执行了我们传入的任务,并且在执行任务之前和执行任务之后还可以调用相关的方法

接下来我们来观察一下是如何将任务加入到队列中的.
在这里插入图片描述
底层的工作队列是阻塞队列,任务会通过offer方法加入到队列中,此时如果队列中没有任务,线程来队列中获取任务时,就会阻塞在这个位置,直到有任务加入到队列中.所以使用阻塞队列,也实现了核心线程的保活

内置线程池

除了使用JUC包下的ThreadPoolExecutor来创建线程,我们还可以使用JUC包下的工具类Executor创建具有一定固定功能的线程池
建立一个任务(后面的几个实验也是使用此任务执行)

      class MyRunnable implements Runnable{@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"执行任务");try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}}
}

newFixedThreadPool

newFixedThreadPool:创建固定线程数量的线程池

使用newFixedThreadPool创建一个线程数为4的线程池

public class test1 {public static void main(String[] args) throws InterruptedException {ExecutorService threadPool= Executors.newFixedThreadPool(4);for(int i=0;i<10;i++){threadPool.execute(new MyRunnable());}TimeUnit.SECONDS.sleep(15);threadPool.shutdown();System.out.println(threadPool.isShutdown());}
}

运行结果:
在这里插入图片描述
我们发现,这里会重复利用4个线程,不会创建多于4的线程.

SingleThreadExecutor

SingleThreadExecutor:只有一个线程的线程池

public class test2 {public static void main(String[] args) throws InterruptedException {ExecutorService threadPool= Executors.newSingleThreadExecutor();for(int i=0;i<10;i++){threadPool.execute(new MyRunnable());}TimeUnit.SECONDS.sleep(15);threadPool.shutdown();}
}

运行结果:
在这里插入图片描述

newCachedThreadPool

newCachedThreadPool:可以根据实际情况动态调整线程数量的线程池

public class test3 {public static void main(String[] args) {ExecutorService threadPool= Executors.newCachedThreadPool();for(int i =0;i<10;i++){threadPool.execute(new MyRunnable());}threadPool.shutdown();}
}

运行结果:
在这里插入图片描述
我们发现,此时线程池选择创建十个线程来执行这十个任务

ScheduledThreadPool

ScheduledThreadPool:给定的延迟后运行任务或者定期执行任务的线程池

线程池的核心线程是否会被回收?

ThreadPoolExecutor默认不会回收核心线程,即使他们已经空闲了.这是为了减少创建线程的开销,因为核心线程通常是要长期保持活跃的.
但如果线程池是用于周期性使用的场景(也就是线程池忙碌-空闲存在周期性),可以使用allowCoreThreadTimeOut(boolean value)方法的参数设置为true,这样就会回收空闲的核心线程

拒绝策略

我们在阅读源码的过程中,已经关注到了拒绝策略这个问题.也就是说:当线程池已经达到最大并发量的时候,我们需要对新来的任务进行"拒绝".那么线程池中的拒绝策略有哪些呢?

ThreadPoolExecutor.AbortPolicy

抛出RejectedExecutionException直接拒绝新任务的处理(这里线程池默认的拒绝策略)

public class test5 {public static void main(String[] args) {BlockingQueue workQueue=new ArrayBlockingQueue(1);ExecutorService threadPool= new ThreadPoolExecutor(1,2,1L,TimeUnit.SECONDS,workQueue);for(int i=0;i<10;i++){threadPool.execute(new MyRunnable());}threadPool.shutdown();}
}

分析代码:
此处的场景中,线程池的最大并发量是3,但我们需要执行十个任务,那么一定会触发默认的拒绝策略.
运行结果:
在这里插入图片描述
我们看到,这里执行了三个任务,就在主线程中抛出了RejectedExecutionException

ThreadPoolExecutor.CallerRunsPolicy

这个是使用调用execute方法的线程来运行被拒绝的任务,如果执行execute方法的线程已关闭,则会丢弃该任务.

public static void main(String[] args) {                                                                                                                                             BlockingQueue workQueue=new ArrayBlockingQueue(1);                                                                                                                               ThreadPoolExecutor threadPool= new ThreadPoolExecutor(1,2,1L,TimeUnit.SECONDS,workQueue,new ThreadPoolExecutor.CallerRunsPolicy());                                              for(int i=0;i<10;i++){                                                                                                                                                           threadPool.execute(new MyRunnable());                                                                                                                                        }                                                                                                                                                                                threadPool.shutdown();                                                                                                                                                           }                                                                                                                                                                                    
}                                                                                                                                                                                    

分析代码:
这里我们创建了一个最大并发数为3的线程池,并且将拒绝策略设定为ThreadPoolExecutor.CallerRunsPolicy,此时我们在运行过程中,肯定会存在线程达到了最大并发数,触发拒绝策略的情况.而ThreadPoolExecutor.CallerRunsPolicy会将需要执行的任务退回给调用者(即调用execute方法的线程,在此处是main线程),由调用者线程来执行该任务,这样就能够保证所有任务都不会被抛弃,而是被执行

运行结果:
在这里插入图片描述
我们看到这里mian方法执行了任务

ThreadPoolEcecutor.DiscardPolicy

不处理新任务,直接丢掉

public class test6 {public static void main(String[] args) {BlockingQueue workQueue=new ArrayBlockingQueue(1);ThreadPoolExecutor threadPool= new ThreadPoolExecutor(1,2,1L, TimeUnit.SECONDS,workQueue,new ThreadPoolExecutor.DiscardPolicy());for(int i=0;i<10;i++){threadPool.execute(new MyRunnable());}threadPool.shutdown();}
}

运行结果:
在这里插入图片描述
这里我们发现,线程池只执行了三个任务,就结束了,并没有抛出异常或者将任务退回给execute的调用者.

ThreadPoolExecutor.DiscardOldestPolicy

此策略将丢弃掉阻塞队列中最早的任务.
我们来进行一个实验:

public class test7 {private  static int i=1;static class MyRunnable_1 implements Runnable{@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"执行任务1");try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}}static class MyRunnable_2 implements Runnable{@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"执行任务2");try {TimeUnit.SECONDS.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}}}static class MyRunnable_3 implements Runnable{@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"执行任务3");try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}}public static void main(String[] args) throws InterruptedException {BlockingQueue workQueue=new ArrayBlockingQueue(1);ThreadPoolExecutor threadPool= new ThreadPoolExecutor(1,1,1L, TimeUnit.SECONDS,workQueue,new ThreadPoolExecutor.DiscardOldestPolicy());threadPool.execute(new MyRunnable_1());threadPool.execute(new MyRunnable_2());threadPool.execute(new MyRunnable_3());threadPool.shutdown();}
}

分析代码:
这里我们创建了三个任务,分别是MyRunnable_1,MyRunnable_2,MyRunnable_3,我们设定线程池的最大并发数为2,即最多线程数为1,阻塞队列为1,这样三个任务执行的时候,首先会执行第一个任务,而第一个任务需要10s,第二个任务进入阻塞队列中进行等待,第三个任务进入触发拒绝策略,所以会将第二个任务抛弃掉.所以代码最终呈现的结果应该是:任务1,3被执行,任务2被抛弃
运行结果:
在这里插入图片描述

相关文章:

【多线程】线程池(上)

文章目录 线程池基本概念线程池的优点线程池的特点 创建线程池自定义线程池线程池的工作原理线程池源码分析内置线程池newFixedThreadPoolSingleThreadExecutornewCachedThreadPoolScheduledThreadPool 线程池的核心线程是否会被回收?拒绝策略ThreadPoolExecutor.AbortPolicyT…...

ansible 语句+jinjia2+roles

文章目录 1、when语句1、判断表达式1、比较运算符2、逻辑运算符3、根据rc的返回值判断task任务是否执行成功5、通过条件判断路径是否存在6、in 2、when和其他关键字1、block关键字2、rescue关键字3、always关键字 3、ansible循环语句1、基于列表循环(whith_items)2、基于字典循…...

【Docker项目实战】使用Docker部署HumHub社交网络平台

【Docker项目实战】使用Docker部署HumHub社交网络平台 一、HumHub介绍1.1 HumHub简介1.2 HumHub特点1.3 主要使用场景二、本次实践规划2.1 本地环境规划2.2 本次实践介绍三、本地环境检查3.1 检查Docker服务状态3.2 检查Docker版本3.3 检查docker compose 版本四、下载HumHub镜…...

“医者仁术”再进化,AI让乳腺癌筛查迎难而上

世卫组织最新数据显示&#xff0c;我国肿瘤疾病仍然呈上升趋势&#xff0c;肿瘤防控形势依然比较严峻。尤其是像乳腺癌等发病率较高的疾病&#xff0c;早诊断和早治疗意义重大&#xff0c;能够有效降低病死率。 另一方面&#xff0c;中国地域广阔且发展不平衡&#xff0c;各地…...

安卓流式布局实现记录

效果图&#xff1a; 1、导入第三方控件 implementation com.google.android:flexbox:1.1.0 2、布局中使用 <com.google.android.flexbox.FlexboxLayoutandroid:id"id/baggageFl"android:layout_width"match_parent"android:layout_height"wrap_co…...

-bash gcc command not found解决方案(CentOS操作系统)

以 CentOS7 为例&#xff0c;执行以下语句 : yum install gcc如果下载不成功&#xff0c;并且网络没有问题。 执行以下语句 : cp -r /etc/yum.repos.d /etc/yum.repos.d.bakrm -f /etc/yum.repos.d/*.repocurl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.…...

(二)Python输入输出函数

一、输入函数 input函数&#xff1a;用户输入的数据&#xff0c;以字符串形式返回&#xff1b;若需数值类型&#xff0c;则进行类型转换。 xinput("请入你喜欢的蔬菜&#xff1a;") print(x) 二、输出函数 print函数 输出单一数值 x666 print(x) 输出混合类型…...

从调用NCCL到深入NCCL源码

本小白目前研究GPU多卡互连的方案&#xff0c;主要参考NCCL和RCCL进行学习&#xff0c;如有错误&#xff0c;请及时指正&#xff01; 内容还在整理中&#xff0c;近期不断更新&#xff01;&#xff01; 背景介绍 在大模型高性能计算时会需要用到多卡&#xff08;GPU&#xf…...

深入理解Transformer的笔记记录(精简版本)NNLM → Word2Vec

文章的整体介绍顺序为&#xff1a; NNLM → Word2Vec → Seq2Seq → Seq2Seq with Attention → Transformer → Elmo → GPT → BERT 自然语言处理相关任务中要将自然语言交给机器学习中的算法来处理&#xff0c;通常需要将语言数学化&#xff0c;因为计算机机器只认数学符号…...

优选算法第一讲:双指针模块

优选算法第一讲&#xff1a;双指针模块 1.移动零2.复写零3.快乐数4.盛最多水的容器5.有效三角形的个数6.查找总价格为目标值的两个商品7.三数之和8.四数之和 1.移动零 链接: 移动零 下面是一个画图&#xff0c;其中&#xff0c;绿色部分标出的是重点&#xff1a; 代码实现&am…...

智能优化算法-水循环优化算法(WCA)(附源码)

目录 1.内容介绍 2.部分代码 3.实验结果 4.内容获取 1.内容介绍 水循环优化算法 (Water Cycle Algorithm, WCA) 是一种基于自然界水循环过程的元启发式优化算法&#xff0c;由Shah-Hosseini于2012年提出。WCA通过模拟水滴在河流、湖泊和海洋中的流动过程&#xff0c;以及蒸发…...

基于SpringBoot的个性化健康建议平台

1系统概述 1.1 研究背景 随着计算机技术的发展以及计算机网络的逐渐普及&#xff0c;互联网成为人们查找信息的重要场所&#xff0c;二十一世纪是信息的时代&#xff0c;所以信息的管理显得特别重要。因此&#xff0c;使用计算机来管理基于智能推荐的卫生健康系统的相关信息成为…...

Mapsui绘制WKT的示例

步骤 创建.NET Framework4.8的WPF应用在NuGet中安装Mapsui.Wpf 4.1.7添加命名空间和组件 <Window x:Class"TestMapsui.MainWindow"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winf…...

Modbus TCP 西门子PLC指令以太口地址配置以及 Poll Slave调试软件地址配置

1前言 本篇文章讲了 Modbus TCP通讯中的一些以太网端口配置和遇到的一些问题&#xff0c; 都是肝货自己测试的QAQ。 2西门子 SERVER 指令 该指令是让外界设备主动连接此PLC被动连接&#xff0c; 所以这里应该填 外界设备的IP地址。 这边 我因为是电脑的Modbus Poll 主机来…...

MySQL表的基本查询上

1&#xff0c;创建表 前面基础的文章已经讲了很多啦&#xff0c;直接上操作&#xff1a; 非常简单&#xff01;下一个&#xff01; 2&#xff0c;插入数据 1&#xff0c;全列插入 前面也说很多了&#xff0c;直接上操作&#xff1a; 以上插入和全列插入类似&#xff0c;全列…...

MySQL中什么情况下类型转换会导致索引失效

文章目录 1. 问题引入2. 准备工作3. 案例分析3.1 正常情况3.2 发生了隐式类型转换的情况 4. MySQL隐式类型转换的规则4.1 案例引入4.2 MySQL 中隐式类型转换的规则4.3 验证 MySQL 隐式类型转换的规则 5. 总结 如果对 MySQL 索引不了解&#xff0c;可以看一下我的另一篇博文&…...

数据治理的意义

数据治理是一套管理数据资产的流程、策略、规则和控制措施&#xff0c;旨在确保数据的质量、安全性、可用性和合规性。数据治理的目标通常包括但不限于以下几点&#xff1a; 1. **提高数据质量**&#xff1a;确保数据的准确性、一致性、完整性和可靠性。 2. **确保数据安全**…...

快手游戏服务端C++开发一面-面经总结

1、tcp的重传机制有哪几种&#xff1f;具体描述一下 最基本的超时重传 超过时间就会重传 三个重复ACK 快速重传 减少等待超时、 接收方可以发送选择性确认 不用重传整段 乱序到达 可以通知哪些丢失 重复数据重传 2、override和final&#xff1f; override可写可不写 写出来就…...

git的学习使用(认识工作区,暂存区,版本区。添加文件的方法)

学习目标&#xff1a; 学习使用git&#xff0c;并且熟悉git的使用 学习内容&#xff1a; 必备环境&#xff1a;xshell&#xff0c;Ubuntu云服务器 如下&#xff1a; 搭建 git 环境认识工作区、暂存区、版本区git基本操作之添加文件&#xff08;1&#xff09;&#xff1a;gi…...

Series数据去重

目录 准备数据 Series数据去重 DataFrame数据和Series数据去重对比 在pandas中&#xff0c;Series.drop_duplicates(keep, inplace)方法用于删除Series对象中的重复值。 keep&#xff1a; 决定保留哪些重复值。可以取以下三个值之一&#xff1a; first&#xff08;默认值&…...

Python语言核心12个必知语法细节

1. 变量和数据类型 Python是动态类型的&#xff0c;变量不需要声明类型。 python复制代码 a 10 # 整数 b 3.14 # 浮点数 c "Hello" # 字符串 d [1, 2, 3] # 列表 2. 条件语句 使用if, elif, else进行条件判断。 python复制代码 x 10 if x > 5: print(&q…...

解决ImageIO无法读取部分JPEG格式图片问题

解决ImageIO无法读取部分JPEG格式图片问题 问题描述 我最近对在线聊天功能进行了一些内存优化&#xff0c;结果在回归测试时&#xff0c;突然发现有张图片总是发送失败。测试同事把问题转到我这儿来看&#xff0c;我仔细检查了一下&#xff0c;发现是上传文件的接口报错&#…...

使用three.js 实现蜡烛效果

使用three.js 实现蜡烛效果 import * as THREE from "three" import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"var scene new THREE.Scene(); var camera new THREE.PerspectiveCamera(60, window.innerWidth / window.in…...

手动在Linux服务器上部署并运行SpringBoot项目(新手向)

背景 当我们在本地开发完应用并且测试通过后&#xff0c;接着就要部署在服务器上启动。 步骤 1.先用maven将SpringBoot应用当成jar包 2.生成jar文件并复制此文件 3.xshell远程连接linux服务器&#xff0c;在xftp将文件粘贴到linux服务器&#xff0c;这里我放在/usr/local…...

自媒体短视频如何制作?

从0到1打造爆款短视频!300条视频创作经验分享,助你玩转自媒体! 想用短视频玩转自媒体却不知道从何下手?别担心!从21年开始接触短视频的我,断断续续创作了300多条视频,踩过不少坑,也收获了一些心得,核心秘诀就是:账号内容垂直化 + 明确受众群体! 我将从主题确定、脚本…...

2024年河南省职业技能竞赛(网络建设与运维赛项)

模块二&#xff1a;网络建设与调试 说明&#xff1a; 1.所网络设备在创建之后都可以直接通过 SecureCRT 软件 telnet 远程连接操作。 2.要求在全员化竞赛平台中保留竞赛生成的所有虚拟主机。 3.题目中所有所有的密码均为 Pass-1234&#xff0c;若未按照要求设置&#xff0c;涉 …...

git--git reset

HEAD 单独一个HEAD eg:git diff HEAD 表示当前结点。 HEAD~ HEAD~只处理当前分支。 注意&#xff1a;master分支的上一个结点是tmp分支的所在的结点fc11b74, 79f109e才是master的第二个父节点。 HEAD~ 当前结点的父节点。 HEAD~1 当前结点的父节点。 HEAD~n 当前结点索…...

Spring Boot的实用内置功能详解

Spring Boot作为一款备受欢迎的Java框架&#xff0c;以其简洁、高效和易用的特点&#xff0c;赢得了广大开发者的青睐。其内置的多种功能更是为开发者提供了极大的便利&#xff0c;本文将详细介绍Spring Boot中记录请求数据、请求/响应包装器、特殊的过滤器Filter以及Controlle…...

撸猫变梳毛?怎么解决猫咪掉毛问题?好用的宠物空气净化器推荐

秋风一吹&#xff0c;新一轮的猫咪换毛季又到了&#xff0c;这也意味着我失去了撸猫自由。我每天的治愈方式就是下班撸猫&#xff0c;抚摸着柔软的毛发&#xff0c;好像一天的烦恼都消除了。可是一到换毛季&#xff0c;猫还没撸两下&#xff0c;先从猫咪身上带下一手毛&#xf…...

人声分离免费软件,六款好用软件处理音乐更轻松!

在这个数字化音乐时代&#xff0c;无论是专业音乐人还是音乐爱好者&#xff0c;都渴望在创作与编辑过程中拥有更多便捷高效的工具。人声分离技术&#xff0c;作为音乐后期制作中的一项关键技术&#xff0c;能够精准地将歌曲中的人声与伴奏分离&#xff0c;极大地拓宽了音乐创作…...