Spring boot - Task Execution and Scheduling @Async

SpringBoot的任务执行器

Spring Boot通过auto-configuration机制自动创建了任务执行器Task Execution,因此在SpringBoot项目中,你不需要任何配置、也不需要自己创建Task Execution就可以直接使用它。

Spring Boot通过auto-configuration机制创建的任务执行器有以下作用:

  1. asynchronous task execution (@EnableAsync):通过@EnableAsync以及@Async注解使用任务执行器。
  2. Spring for GraphQL’s asynchronous handling of Callable return values from controller methods(这个没用过)。
  3. Spring MVC’s asynchronous request processing:Spring MVC的异步请求处理。
  4. Spring WebFlux’s blocking execution support

除以上官网提到的,你还可以:手动使用任务执行器执行异步任务。

SpringBoot通过auto-configuration机制帮你创建了任务执行器TaskExecution,至于怎么通过TaskExecution、执行什么异步任务当然是你自己的事情了。

今天研究两部分内容:

  1. SpringBoot的auto-configuration机制创建任务执行器的过程。
  2. 通过任务执行器执行任务,主要是上述提到的官网内容第1项@EnableAsync以及@Async注解的原理及使用。

Spring Boot创建任务执行器

SpringBoot官网说的很明确,TaskExecutor通过SpringBoot的auto-configuration技术创建。我们知道SpringBoot的auto-configuration技术(详情请参考:SpringBoot 自动配置@EnableAutoConfiguration)是通过META-INF/spring.factories文件指定自动配置内容的,我们打开spring.factories文件找一下taskExecutor的相关内容,在EnableAutoConfiguration项下果然发现了TaskExecutionAutoConfiguration:
在这里插入图片描述
TaskExecutionAutoConfiguration在org.springframework.boot.autoconfigure.task包下,代码不算长,比较简单:

@ConditionalOnClass(ThreadPoolTaskExecutor.class)
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(TaskExecutionProperties.class)
public class TaskExecutionAutoConfiguration {/*** Bean name of the application {@link TaskExecutor}.*/public static final String APPLICATION_TASK_EXECUTOR_BEAN_NAME = "applicationTaskExecutor";@Bean@ConditionalOnMissingBeanpublic TaskExecutorBuilder taskExecutorBuilder(TaskExecutionProperties properties,ObjectProvider<TaskExecutorCustomizer> taskExecutorCustomizers,ObjectProvider<TaskDecorator> taskDecorator) {TaskExecutionProperties.Pool pool = properties.getPool();TaskExecutorBuilder builder = new TaskExecutorBuilder();builder = builder.queueCapacity(pool.getQueueCapacity());builder = builder.corePoolSize(pool.getCoreSize());builder = builder.maxPoolSize(pool.getMaxSize());builder = builder.allowCoreThreadTimeOut(pool.isAllowCoreThreadTimeout());builder = builder.keepAlive(pool.getKeepAlive());Shutdown shutdown = properties.getShutdown();builder = builder.awaitTermination(shutdown.isAwaitTermination());builder = builder.awaitTerminationPeriod(shutdown.getAwaitTerminationPeriod());builder = builder.threadNamePrefix(properties.getThreadNamePrefix());builder = builder.customizers(taskExecutorCustomizers.orderedStream()::iterator);builder = builder.taskDecorator(taskDecorator.getIfUnique());return builder;}@Lazy@Bean(name = { APPLICATION_TASK_EXECUTOR_BEAN_NAME,AsyncAnnotationBeanPostProcessor.DEFAULT_TASK_EXECUTOR_BEAN_NAME })@ConditionalOnMissingBean(Executor.class)public ThreadPoolTaskExecutor applicationTaskExecutor(TaskExecutorBuilder builder) {return builder.build();}}

它当然是一个Configuration类,在SpringBoot启动的过程@Bean注解指定的方法会被加载到Spring IoC容器中。其次,通过@EnableConfigurationProperties指定了配置类TaskExecutionProperties。

被加载到Ioc容器中的有两个对象:一个是通过taskExecutorBuilder方法加载的TaskExecutorBuilder,另外一个是通过applicationTaskExecutor方法加载的ThreadPoolTaskExecutor。

分别看一下。

TaskExecutorBuilder的创建

TaskExecutor构建器,在applicationTaskExecutor方法中负责构建TaskExecutor。

taskExecutorBuilder方法会接收一个参数TaskExecutionProperties ,用来指定TaskExecutor的各属性,比如queueCapacity、coreSize、keepAlive等等线程池相关参数,线程池相关内容请参考:线程池 - ThreadPoolExecutor源码分析。

简单看一眼TaskExecutionProperties类:

@ConfigurationProperties("spring.task.execution")
public class TaskExecutionProperties {private final Pool pool = new Pool();private final Shutdown shutdown = new Shutdown();/*** Prefix to use for the names of newly created threads.*/private String threadNamePrefix = "task-";public Pool getPool() {return this.pool;}public Shutdown getShutdown() {return this.shutdown;}public String getThreadNamePrefix() {return this.threadNamePrefix;}public void setThreadNamePrefix(String threadNamePrefix) {this.threadNamePrefix = threadNamePrefix;}public static class Pool {/*** Queue capacity. An unbounded capacity does not increase the pool and therefore* ignores the "max-size" property.*/private int queueCapacity = Integer.MAX_VALUE;/*** Core number of threads.*/private int coreSize = 8;/*** Maximum allowed number of threads. If tasks are filling up the queue, the pool* can expand up to that size to accommodate the load. Ignored if the queue is* unbounded.*/private int maxSize = Integer.MAX_VALUE;/*** Whether core threads are allowed to time out. This enables dynamic growing and* shrinking of the pool.*/private boolean allowCoreThreadTimeout = true;/*** Time limit for which threads may remain idle before being terminated.*/private Duration keepAlive = Duration.ofSeconds(60);public int getQueueCapacity() {return this.queueCapacity;}public void setQueueCapacity(int queueCapacity) {this.queueCapacity = queueCapacity;}public int getCoreSize() {return this.coreSize;}public void setCoreSize(int coreSize) {this.coreSize = coreSize;}public int getMaxSize() {return this.maxSize;}public void setMaxSize(int maxSize) {this.maxSize = maxSize;}public boolean isAllowCoreThreadTimeout() {return this.allowCoreThreadTimeout;}public void setAllowCoreThreadTimeout(boolean allowCoreThreadTimeout) {this.allowCoreThreadTimeout = allowCoreThreadTimeout;}public Duration getKeepAlive() {return this.keepAlive;}public void setKeepAlive(Duration keepAlive) {this.keepAlive = keepAlive;}}

正是@EnableConfigurationProperties以及@ConfigurationProperties注解决定了我们可以在配置文件(比如application.yml)中指定TaskExecutionProperties中的这些有关线程池的参数。

接收到这些配置参数之后,使用配置参数创建TaskExecutorBuilder,交给Spring Ioc容器。

ThreadPoolTaskExecutor 的创建

applicationTaskExecutor方法通过上面创建出来的TaskExecutorBuilder的build方法创建。

TaskExecutorBuilderd的build方法:

	public ThreadPoolTaskExecutor build() {return configure(new ThreadPoolTaskExecutor());}

new了一个ThreadPoolTaskExecutor对象,调用configure方法:

	public <T extends ThreadPoolTaskExecutor> T configure(T taskExecutor) {PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();map.from(this.queueCapacity).to(taskExecutor::setQueueCapacity);map.from(this.corePoolSize).to(taskExecutor::setCorePoolSize);map.from(this.maxPoolSize).to(taskExecutor::setMaxPoolSize);map.from(this.keepAlive).asInt(Duration::getSeconds).to(taskExecutor::setKeepAliveSeconds);map.from(this.allowCoreThreadTimeOut).to(taskExecutor::setAllowCoreThreadTimeOut);map.from(this.awaitTermination).to(taskExecutor::setWaitForTasksToCompleteOnShutdown);map.from(this.awaitTerminationPeriod).as(Duration::toMillis).to(taskExecutor::setAwaitTerminationMillis);map.from(this.threadNamePrefix).whenHasText().to(taskExecutor::setThreadNamePrefix);map.from(this.taskDecorator).to(taskExecutor::setTaskDecorator);if (!CollectionUtils.isEmpty(this.customizers)) {this.customizers.forEach((customizer) -> customizer.customize(taskExecutor));}return taskExecutor;}

将配置文件传递过来的参数传递给创建出来的ThreadPoolTaskExecutor对象并返回。

不配置的情况下,线程池默认参数在TaskExecutionProperties中指定:
在这里插入图片描述
ThreadPoolTaskExecutor创建完成!

TaskExecutor的使用

既然Spring Boot已经帮助我们完成了TaskExecutor的创建并注入了Spring Ioc容器中,接下来我们就看一下该怎么使用它。

首先要尝试的是“手动使用”,不使用Spring的注解、而是想办法在代码中直接从Spring容器中获取到TaskExecutor之后调用他的execute方法。

首先创建一个Spring Boot项目,不需要什么特殊功能,pom文件也很简单,引入spring-web即可:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.6.5</version>
<!--        <version>3.1.4</version>--><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.example</groupId><artifactId>springbootstart</artifactId><version>0.0.1-SNAPSHOT</version><name>springbootstart</name><description>springbootstart</description><properties><java.version>17</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin></plugins></build></project>

然后,创建一个userService:


@Service
@Slf4j
public class UserService {@Autowiredprivate TaskExecutor taskExecutor;public void test2(){taskExecutor.execute(()->{log.info("this is userservice test2 start...");try{Thread.sleep(10000);}catch (Exception e){}log.info("This is userService' test2 end...");});}
}

userService非常简单,比较重要的是:

    @Autowiredprivate TaskExecutor taskExecutor;

这行代码通过@Autowired自动装配一个TaskExecutor 对象,因为我们从前面对Spring Boot代码的分析,Spring Boot应该是在启动的过程中已经通过auto-configuration机制自动创建并注入了TaskExecutor,所以按道理我们是可以通过自动装配的方式在userService中应用它的。

然后写一个test2方法,log看一下装配进来的taskExecutor到底是个啥对象,再调用taskExecutor的execute的方法模拟异步执行任务,执行前后打印log。

然后,写controller:

@RestController
@RequestMapping("/hello")
@Slf4j
public class HelloWorldController {public HelloWorldController(){}@Autowiredprivate UserService userService;@GetMapping ("/test2")public String test2(){userService.test2();log.info("after userservice test2 ...");return "hello";}
}

OK,代码准备好了,启动应用,测试。通过应用的端口号8002可以正常访问:
在这里插入图片描述

而且,结果可以立即返回,前台并不需要等待userService的test2方法中睡眠的10秒钟,说明睡眠的线程一定是通过taskExecutor调用起来的异步线程,taskExecutor一定是生效了。

后台log也说明确实如此:
在这里插入图片描述
前面的log是前台调用接口、tomcat的线程nio-8002-exec-1打印的,之后taskExecutor启动了新线程task-1,后面的两行日志是线程task-1打印的。

@Async注解

自己写代码使用taskExecutor线程池启动新线程执行任务这种方式虽然行得通,但是太low太繁琐了,既然使用了Spring框架,我们当然不需要这么麻烦。Spring给我们提供了@Async注解。

@Async注解可以用在方法上,也可以用在类上,不管用在方法上、还是用在类上,都要求当前类必须是受Spring管理的bean,因为@Async注解是通过Spring的BeanPostProcessor机制生效的。

我们改造UserService类,再编写一个test方法:

    @Asyncpublic void test(){log.info("This is userService' test start...");try {Thread.sleep(10000);}catch (Exception e){}log.info("This is userService' test end...");return;}

代码逻辑也非常简单,和test2方法一样,睡眠10秒后才返回结果。

重新启动应用后测试,发现@Async没有生效!

不生效的原因是缺少@EnableAsync注解,在启动类增加@EnableAsync注解后重新测试,发现@Async生效了,测试结果和test2的一样,所以也就不贴图了。

接下来的任务是,研究@EnableAsync注解的作用,为什么没有@EnableAsync注解的情况下,@Async注解不能生效。

@EnableAsync注解的底层原理

关于Spring的@Enablexxx注解,我们前面的文章分析过,基本就是通过@Configuration+@Import注解的联合使用达到注入指定对象到Spring IoC容器中。

先看@EnableAsync源码:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AsyncConfigurationSelector.class)
public @interface EnableAsync {

通过@Import注解引入AsyncConfigurationSelector类,继续跟踪AsyncConfigurationSelector代码:

public class AsyncConfigurationSelector extends AdviceModeImportSelector<EnableAsync> {private static final String ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME ="org.springframework.scheduling.aspectj.AspectJAsyncConfiguration";/*** Returns {@link ProxyAsyncConfiguration} or {@code AspectJAsyncConfiguration}* for {@code PROXY} and {@code ASPECTJ} values of {@link EnableAsync#mode()},* respectively.*/@Override@Nullablepublic String[] selectImports(AdviceMode adviceMode) {switch (adviceMode) {case PROXY:return new String[] {ProxyAsyncConfiguration.class.getName()};case ASPECTJ:return new String[] {ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME};default:return null;}}}

扩展了AdviceModeImportSelector类,而AdviceModeImportSelector类实现了ImportSelector接口,而ImportSelector接口这种方式最终是通过他的方法selectImports来实现注入的(这部分可以参考 SpringBoot 自动配置@EnableAutoConfiguration)。

selectImports方法根据adviceMode(默认是PROXY)会引入ProxyAsyncConfiguration类:

@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyAsyncConfiguration extends AbstractAsyncConfiguration {@Bean(name = TaskManagementConfigUtils.ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME)@Role(BeanDefinition.ROLE_INFRASTRUCTURE)public AsyncAnnotationBeanPostProcessor asyncAdvisor() {Assert.notNull(this.enableAsync, "@EnableAsync annotation metadata was not injected");AsyncAnnotationBeanPostProcessor bpp = new AsyncAnnotationBeanPostProcessor();bpp.configure(this.executor, this.exceptionHandler);Class<? extends Annotation> customAsyncAnnotation = this.enableAsync.getClass("annotation");if (customAsyncAnnotation != AnnotationUtils.getDefaultValue(EnableAsync.class, "annotation")) {bpp.setAsyncAnnotationType(customAsyncAnnotation);}bpp.setProxyTargetClass(this.enableAsync.getBoolean("proxyTargetClass"));bpp.setOrder(this.enableAsync.<Integer>getNumber("order"));return bpp;}}

ProxyAsyncConfiguration 是一个配置类,会通过@Bean注解注入一个叫AsyncAnnotationBeanPostProcessor 的BeanPostProcessor。从类名称我们就可以猜测到@Async注解就是通过这个后置处理器进行处理的。

接下来的代码跟踪还是稍稍有点复杂的。

首先,AsyncAnnotationBeanPostProcessor 通过父类AbstractBeanFactoryAwareAdvisingPostProcessor实现了BeanFactoryAware接口,所以我们知道他的setBeanFactory方法在Spring的Bean创建过程中会被回调:

	@Overridepublic void setBeanFactory(BeanFactory beanFactory) {super.setBeanFactory(beanFactory);AsyncAnnotationAdvisor advisor = new AsyncAnnotationAdvisor(this.executor, this.exceptionHandler);if (this.asyncAnnotationType != null) {advisor.setAsyncAnnotationType(this.asyncAnnotationType);}advisor.setBeanFactory(beanFactory);this.advisor = advisor;}

setBeanFactory方法中会创建一个advisor类AsyncAnnotationAdvisor,从名字中我们又可以猜测到,@Async注解最终应该会通过AOP技术实现。

继续跟踪AsyncAnnotationAdvisor源码,构造器:

	public AsyncAnnotationAdvisor(@Nullable Supplier<Executor> executor, @Nullable Supplier<AsyncUncaughtExceptionHandler> exceptionHandler) {Set<Class<? extends Annotation>> asyncAnnotationTypes = new LinkedHashSet<>(2);asyncAnnotationTypes.add(Async.class);try {asyncAnnotationTypes.add((Class<? extends Annotation>)ClassUtils.forName("javax.ejb.Asynchronous", AsyncAnnotationAdvisor.class.getClassLoader()));}catch (ClassNotFoundException ex) {// If EJB 3.1 API not present, simply ignore.}this.advice = buildAdvice(executor, exceptionHandler);this.pointcut = buildPointcut(asyncAnnotationTypes);}

调用buildAdvice和buildPointcut,创建切面和切点:

	protected Advice buildAdvice(@Nullable Supplier<Executor> executor, @Nullable Supplier<AsyncUncaughtExceptionHandler> exceptionHandler) {AnnotationAsyncExecutionInterceptor interceptor = new AnnotationAsyncExecutionInterceptor(null);interceptor.configure(executor, exceptionHandler);return interceptor;}

构造切面的方法会创建一个AnnotationAsyncExecutionInterceptor 类,回忆一下AOP相关知识,我们知道Pointcut满足的情况下会调用切面类的invoke方法。

构造Pointcut的方法源码我们就不再跟踪了,可以猜测到他的匹配逻辑应该是检查当前方法(或者当前类)是否有@Async注解。

接下来我们就继续跟踪AnnotationAsyncExecutionInterceptor 类。

AnnotationAsyncExecutionInterceptor继承自父类AsyncExecutionInterceptor,invoke方法在他父类AsyncExecutionInterceptor中。

	public Object invoke(final MethodInvocation invocation) throws Throwable {Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);Method specificMethod = ClassUtils.getMostSpecificMethod(invocation.getMethod(), targetClass);final Method userDeclaredMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);AsyncTaskExecutor executor = determineAsyncExecutor(userDeclaredMethod);if (executor == null) {throw new IllegalStateException("No executor specified and no default executor set on AsyncExecutionInterceptor either");}Callable<Object> task = () -> {try {Object result = invocation.proceed();if (result instanceof Future) {return ((Future<?>) result).get();}}catch (ExecutionException ex) {handleError(ex.getCause(), userDeclaredMethod, invocation.getArguments());}catch (Throwable ex) {handleError(ex, userDeclaredMethod, invocation.getArguments());}return null;};return doSubmit(task, executor, invocation.getMethod().getReturnType());}

invoke方法就是实现异步调用的地方!

首先会通过determineAsyncExecutor方法获取taskExecutor,这也是我们关心的地方,不过我们先放放,先看一下拿到TaskExecutor之后的处理逻辑。

代码并不复杂,lamda方式创建一个callable任务,通过invocation.proceed()执行原方法。

通过doSubmit方法、使用TaskExecutor启动新的线程调用task任务、完成对原方法的执行!

主要代码跟踪完毕。

最后,再来看一下determineAsyncExecutor方法:

	protected AsyncTaskExecutor determineAsyncExecutor(Method method) {AsyncTaskExecutor executor = this.executors.get(method);if (executor == null) {Executor targetExecutor;String qualifier = getExecutorQualifier(method);if (StringUtils.hasLength(qualifier)) {targetExecutor = findQualifiedExecutor(this.beanFactory, qualifier);}else {targetExecutor = this.defaultExecutor.get();}if (targetExecutor == null) {return null;}executor = (targetExecutor instanceof AsyncListenableTaskExecutor ?(AsyncListenableTaskExecutor) targetExecutor : new TaskExecutorAdapter(targetExecutor));this.executors.put(method, executor);}return executor;}

总体的逻辑就是,通过beanFactory从Spring Ioc容器中获取TaskExecutor,首先判断是否有QualifiedExecutor,有的话通过findQualifiedExecutor方法从容器中获取QualifiedExecutor,没有的话通过this.defaultExecutor.get()获取。

this.defaultExecutor.get()的业务逻辑需要基于接口AsyncConfigurer来解释:

public interface AsyncConfigurer {/*** The {@link Executor} instance to be used when processing async* method invocations.*/@Nullabledefault Executor getAsyncExecutor() {return null;}/*** The {@link AsyncUncaughtExceptionHandler} instance to be used* when an exception is thrown during an asynchronous method execution* with {@code void} return type.*/@Nullabledefault AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {return null;}}

AsyncConfigurer 接口有两个方法,一个用来获取Executor,一个用来获取AsyncUncaughtExceptionHandler。

this.defaultExecutor.get()的业务逻辑大概可以概括为:如果应用实现了AsyncConfigurer接口,则通过该接口获取Executor,否则,如果没有提供AsyncConfigurer的实现类,则向Spring Ioc容器获取默认的TaskExecutor:
在这里插入图片描述
OK,Thanks a lot!

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

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

相关文章

nodejs+vue+ElementUi银行贷款业务管理系统

银行贷款管理系统的主要实现功能包括&#xff1a;管理员&#xff1a;首页、个人中心、用户管理、银行管理、贷款信息管理、贷款申请管理、金额发布管理、还款信息管理、通知信息管理&#xff0c;用户&#xff1a;首页、个人中心、贷款信息管理、贷款申请管理、金额发布管理、还…

phpinfo和php -m 加载的php.ini不一致

目的&#xff1a; 将phpinfo在web中展示的php.ini和在命令行中展示的php.ini加载路径设置一致。 原本的php.ini加载路劲是&#xff1a; /usr/local/lib/php.ini 解决思路&#xff1a; &#xff08;1&#xff09;which php 查看服务器加载的php的位置&#xff0c;这里原来是&a…

差分算法模板

差分算法模板 一维差分一维insert函数(构造差分数组和实现区域加数操作)一维差分模板题 二维差分二维insert函数(构造差分数组和实现区域加数操作)二维差分模板题 一维差分 差分主要是计算出某个区域段的数分别加上一个数 先给定一个原数组a&#xff1a;a[1], a[2], a[3], a[n]…

CNN:Convolutional Neural Network(上)

目录 1 为什么使用 CNN 处理图像 2 CNN 的整体结构 2.1 Convolution 2.2 Colorful image 3 Convolution v.s. Fully Connected 4 Max Pooling 5 Flatten 6 CNN in Keras 原视频&#xff1a;李宏毅 2020&#xff1a;Convolutional Neural Network 1 为什么使用…

NumPy:从初识到实战,探索Python科学计算的无限可能

NumPy 在浩瀚的Python编程世界中&#xff0c;有一个强大的库如星辰般璀璨&#xff0c;它是数据科学家、机器学习工程师乃至量化金融分析师手中的利器——NumPy&#xff0c;它以其高效的数据处理能力和便捷的矩阵运算机制&#xff0c;在科研与工程领域中占据着举足轻重的地位。…

Java BIO、NIO(通信/群聊系统、零拷贝)、AIO

Java BIO、NIO(通信/群聊系统、零拷贝)、AIO BIO、NIO、AIO特点和场景 BIO&#xff08;Blocking I/O&#xff09;、NIO&#xff08;Non-blocking I/O&#xff09;、AIO&#xff08;Asynchronous I/O&#xff09;是Java中用于处理I/O操作的三种不同的I/O模型&#xff0c;它们具…

day16 二叉树的最大深度 n叉树的最大深度 二叉树的最小深度 完全二叉树的节点数

题目1&#xff1a;104 二叉树的最大深度 题目链接&#xff1a;104 二叉树的最大深度 题意 二叉树的根节点是root&#xff0c;返回其最大深度&#xff08;从根节点到最远叶子节点的最长路径上的节点数&#xff09; 递归 根节点的的高度就是二叉树的最大深度 所以使用后序遍…

系列六、Spring Security中的认证 授权 角色继承

一、Spring Security中的认证 & 授权 & 角色继承 1.1、概述 关于Spring Security中的授权&#xff0c;请参考【系列一、认证 & 授权】&#xff0c;这里不再赘述。 1.2、资源类 /*** Author : 一叶浮萍归大海* Date: 2024/1/11 20:58* Description: 测试资源*/ Re…

思科模拟器中环境条件设置第一步

一直设想若上图中的温度在不同房间显示和物理环境一样的结果。 于是乎看了 经过实验 原因是 在模拟器的physcial中设置就可以实现不同的温度。 接下来就是合理使用了&#xff01; 对类似实验感兴趣的同学&#xff0c;一起加入尝试&#xff1a;微信号imaguofeng 2024年1月14…

计算机找不到vcomp140.dll怎样修复?马上教会你修复dll问题

在计算机系统运行过程中&#xff0c;遭遇“vcomp140.dll丢失”的场景并不少见&#xff0c;这一问题的出现往往伴随着软件无法正常启动、运行时错误提示或者系统性能下降等现象。具体场景可能包括但不限于&#xff1a;用户在尝试打开某个依赖于Visual C Redistributable库的应用…

数据在AI图像修复任务中的核心作用

在人工智能&#xff08;AI&#xff09;领域&#xff0c;数据的重要性不言而喻。尤其在图像修复任务中&#xff0c;数据的精度和质量直接影响着AI模型的性能。图像修复是指利用AI技术自动识别图像中的缺陷或遮挡物&#xff0c;并对其进行修复或还原的过程。这项技术广泛应用于各…

基于pyradiomics影像组学特征提取

基于pyradiomics影像组学特征提取 特征提取&#xff1a;1 pyradiomics的使用&#xff1a;1.1&#xff0c;在python环境下安装pyradiomics:1.2,设置特征提取器&#xff0c;获得想要特征&#xff1a;1.2.1 图像类型1.2.2 目标特征设置1.2.3 特征提取器设置 2 代码示例;参考&#…

【web服务搭建实验】之nginx基础学习

目录 一、nginx的简介二、nginx安装实验虚拟主机的配置web服务器的主流实现方式-LAMP和LNMP 一、nginx的简介 Nginx是一款轻量级HTTP服务器&#xff0c;同时也是代理邮箱服务器&#xff0c;具备反向代理&#xff0c;通用代理的功能。支持多个系统&#xff0c;和不同操作系统。…

【算法】Java-二叉树的右视图(BFS、DFS两种解法)

题目要求&#xff1a; 给定一个二叉树的 根节点 root&#xff0c;想象自己站在它的右侧&#xff0c;按照从顶部到底部的顺序&#xff0c;返回从右侧所能看到的节点值。 示例 1: 输入: [1,2,3,null,5,null,4] 输出: [1,3,4]示例 2: 输入: [1,null,3] 输出: [1,3]示例 3: 输入…

e2studio开发三轴加速度计LIS2DW12(3)----检测活动和静止状态

e2studio开发三轴加速度计LIS2DW12.3--检测活动和静止状态 概述视频教学样品申请源码下载新建工程工程模板保存工程路径芯片配置工程模板选择时钟设置UART配置UART属性配置设置e2studio堆栈e2studio的重定向printf设置R_SCI_UART_Open()函数原型回调函数user_uart_callback ()…

Jetpack Compose -> 声明式UI Modifier

前言 本章主要介绍下 Compose 的声明式 UI 以及初级写法&#xff1b; 什么是声明式UI 传统UI 传统 UI 方式来声明UI <androidx.appcompat.widget.LinearLayoutCompat android:layout_width"match_parent" android:layout_height"match_parent&quo…

test Property-based Testing-04-junit-quickcheck

拓展阅读 开源 Auto generate mock data for java test.(便于 Java 测试自动生成对象信息) 开源 Junit performance rely on junit5 and jdk8.(java 性能测试框架。性能测试。压测。测试报告生成。) junit-quickcheck&#xff1a;基于 JUnit 风格的属性驱动测试库 junit-qu…

送水小程序开发:如何选择最适合的技术平台

选择最适合的技术平台对于开发送水小程序至关重要。在这篇文章中&#xff0c;我将为您介绍如何选择最适合的技术平台来开发送水小程序&#xff0c;以及各种技术平台的优缺点。无论您是刚刚起步的创业公司还是成熟的送水服务提供商&#xff0c;本文都将为您提供有关送水小程序开…

深信服超融合HCI版本升级,6.0.0R5升级至6.8.0R2

超融合升级&#xff0c;需要满足以下条件及前期准备&#xff1a; 确认HCI的升级序列号有效升级时长大概在一个半小时&#xff0c;安全起见&#xff0c;需预留至少三至四小时窗口期升级前&#xff0c;需要将所有虚拟机关机&#xff0c;涉及到业务无法访问&#xff0c;需提前通知…

网络-DHCP中继(思科)

思科 前提&#xff1a;将R1 R3配置16板卡 将R1更改标识符为三层交换机 将R3更改标识符为交换机 拓扑图&#xff1a; R2进行配置 配置IP地址 为12.0.0.2 配置默认路由到R1的f1/4接口 配置dhcp地址池 配置vlan10的地址池 配置vlan20的地址池 三层交换机R1进行配置 将f1/4接口…