Spring系列-9 Async注解使用与原理
背景:
本文作为Spring系列的第九篇,介绍@Async注解的使用、注意事项和实现原理,原理部分会结合Spring框架代码进行。
本文可以和Spring系列-8 AOP原理进行比较阅读
1.使用方式
@Async一般注解在方法上,用于实现方法的异步:方法调用者立即返回,待调用的方法提交给Spring的线程池执行。@Async也可以注解在类上,等价于在类中的所有方法上添加该注解。需要注意@Async只对Spring管理的对象生效。
案例介绍:
1.1 基本用例
在配置类或者启动类上注解@EnableAsync,用于开启异步功能:
@Configuration
@ComponentScan(basePackages = "com.seong.async")
@EnableAsync
public class AsyncConfiguration {
}
在方法上添加@Async注解:
package com.seong.async;@Component
public class ComponentA {@Asyncpublic void print() {System.out.println(Thread.currentThread().getName()+":"+" test Async call.");}
}
用例如下:
@Slf4j
public class AsyncApplication {public static void main(String[] args) {AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AsyncConfiguration.class);//用id取出业务逻辑类的beanComponentA componentA = (ComponentA) applicationContext.getBean("componentA");LOGGER.info("test begin...");componentA.print();LOGGER.info("test end!");}
}
得到如下结果:
结果显示:ComponentA对象的print()方法被异步执行了,且DEBUG日志抛出了No qualifying bean of type 'org.springframework.core.task.TaskExecutor' available
异常;这是因为业务代码没有配置TaskExecutor类型的Bean对象导致。
1.2 配置线程池
被@Async注解的方法会提交给线程池执行,这里可以手动指定线程池或者使用默认的线程池:
(1) 手动指定线程池:
通过Async的value属性指定线程池Bean对象(通过beanName指定),如:
@Component
public class ComponentA {@Async("myTaskExecutor")public void print() {System.out.println(Thread.currentThread().getName()+":"+" test Async call.");}
}@Configuration
@EnableAsync
public class AsyncConfiguration {@Beanpublic Executor myTaskExecutor() {return Executors.newFixedThreadPool(1);}
}
此时,运行任务会提交给"myTaskExecutor"线程池对象执行。
(2) 配置默认的线程池:
@Bean
public TaskExecutor myTaskExecutor() {return new SimpleAsyncTaskExecutor();
}
Spring为@Async提供了默认线程池配置,可通过向IOC中注册TaskExecutor类型的Bean对象实现。
也可使用如下配置注册默认线程池:
@Bean
public Executor taskExecutor() {return Executors.newFixedThreadPool(1);
}
Spring框架获取TaskExecutor类型的Bean对象失败时,会尝试获取BeanName为"taskExecutor"的线程池对象;但执行时日志中会给出异常信息。
(3) 使用Spring框架默认的SimpleAsyncTaskExecutor线程池.
若业务未配置默认线程池,默认使用Spring生成的SimpleAsyncTaskExecutor对象; 但执行时日志中会给出异常信息。
1.3 获取返回值
由于@Async注解的方法为异步执行,因此可以通过Future来获取返回值,案例如下:
修改ComponentA的方法:
@Component
@Slf4j
public class ComponentA {@Asyncpublic Future<String> print() {LOGGER.info(Thread.currentThread().getName() + ":" + " test Async call.");return new AsyncResult<>("ComponentA finished.");}
}
适配修改案例:
@Slf4j
public class AsyncApplication {public static void main(String[] args) throws ExecutionException, InterruptedException {AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AsyncConfiguration.class);ComponentA componentA = (ComponentA) applicationContext.getBean("componentA");LOGGER.info("test begin...");final Future<String> resultFuture = componentA.print();doOtherBusiness();LOGGER.info("test end!");// 这一步执行时main线程会等待异步方法返回的结果LOGGER.info("result is {}", resultFuture.get());}private static void doOtherBusiness() {LOGGER.info("do other business...");}
}
运行得到如下结果:
2.原理:
本质上Async注解的实现依赖于动态代理,代理过程中将任务提交给线程池,存在以下流程:
如上图所示,Spring为使用Async注解Bean对象生产一个动态代理对象,当Async注解的方法被调用时,会进入代理流程,选择线程池、封装调用逻辑并提交给线程池执行,返回执行结果。
注意:未被@Async注解的方法则不会执行上述流程(static方法也不会)
由于异步的本质是基于代理实现,所以同一个类中的方法调用会导致被调用方的异步作用失效,该场景与Spring的事务失效原因相同,可参考:事务-1 事务隔离级别和Spring事务传播机制.
2.1 EnableAsync注解:
2.1.1 EnableAsync注解定义
@EnableAsync注解核心作用是向容器中注册AsyncAnnotationBeanPostProcessor对象。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AsyncConfigurationSelector.class)
public @interface EnableAsync {Class<? extends Annotation> annotation() default Annotation.class;boolean proxyTargetClass() default false;AdviceMode mode() default AdviceMode.PROXY;int order() default Ordered.LOWEST_PRECEDENCE;
}
@EnableAsync注解提过了以下属性用于自定义配置:
[1] annotation属性:
用于指定生效的注解,默认为@Async和EJB的@Asynchronous注解。当指定注解时,仅指定的注解生效:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestAnnotation {
}@EnableAsync(annotation = TestAnnotation.class)
public class AsyncConfiguration {
}@TestAnnotation
public void print() {System.out.println(Thread.currentThread().getName() + ":" + " test Async call");
}
但是,仅@Async中可以通过value属性指定需要的线程池Bean对象;因为硬编码获取beanName的逻辑:来源必须为@Async注解的value属性:
protected String getExecutorQualifier(Method method) {Async async = AnnotatedElementUtils.findMergedAnnotation(method, Async.class);if (async == null) {async = AnnotatedElementUtils.findMergedAnnotation(method.getDeclaringClass(), Async.class);}return (async != null ? async.value() : null);
}
[2] proxyTargetClass属性:
可用于配置代理类型:true时表示强制使用CGLIB动态代理,false时表示根据被代理类情况进行确定,默认为false。仅当模式设置为AdviceMode#PROXY时有效。
[3] mode属性:
指出应该如何应用异步通知, 默认值是AdviceMode.PROXY。
[4] order属性:
指示应该Bean对象在BPP阶段应用AsyncAnnotationBeanPostProcessor的顺序,默认值是Ordered.LOWEST_PRECEDENCE,以便在所有其他后处理器之后运行,这样它就可以向现有代理添加一个advisor而不是双代理。
2.1.2 EnableAsync注解作用
通过@Import注解向IOC中注入了AsyncConfigurationSelector对象, 进入AsyncConfigurationSelector对象:
public final String[] selectImports(AnnotationMetadata importingClassMetadata) {//...AdviceMode adviceMode = attributes.getEnum(getAdviceModeAttributeName());String[] imports = selectImports(adviceMode);//...return imports;
}public String[] selectImports(AdviceMode adviceMode) {switch (adviceMode) {case PROXY:// @EnableAsync注解的mode属性的默认值为AdviceMode.PROXYreturn new String[] {ProxyAsyncConfiguration.class.getName()};case ASPECTJ:return new String[] {ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME};default:return null;}
}
由于加载的AsyncConfigurationSelector对象为ImportSelector类型,Spring继续完成ProxyAsyncConfiguration类型的注入。
进入ProxyAsyncConfiguration代码逻辑,发现是一个配置类,用于向容器中注入一个AsyncAnnotationBeanPostProcessor对象:
@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyAsyncConfiguration extends AbstractAsyncConfiguration {@Bean(name="org.springframework.context.annotation.internalAsyncAnnotationProcessor")@Role(BeanDefinition.ROLE_INFRASTRUCTURE)public AsyncAnnotationBeanPostProcessor asyncAdvisor() {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;}
}
总之,@EnableAsync后IOC容器中会增加一个AsyncAnnotationBeanPostProcessor类型的对象;beanName为"org.springframework.context.annotation.internalAsyncAnnotationProcessor".
2.2 AsyncAnnotationBeanPostProcessor:
AsyncAnnotationBeanPostProcessor是一个后置处理器且实现了BeanFactoryAware接口,核心逻辑在两处:Aware阶段和BPP-After阶段.
[1] Aware阶段:准备好Advisor对象(内部包含一个拦截器)
public void setBeanFactory(BeanFactory beanFactory) {super.setBeanFactory(beanFactory);// 构造AsyncAnnotationAdvisor对象,注入到this.advisor属性中AsyncAnnotationAdvisor advisor = new AsyncAnnotationAdvisor(this.executor, this.exceptionHandler);advisor.setBeanFactory(beanFactory);this.advisor = advisor;
}
上述方法完成了BeanFactory属性的注入以及this.advisor属性的设置,属性类型为AsyncAnnotationAdvisor,进入AsyncAnnotationAdvisor的构造函数:
public AsyncAnnotationAdvisor( executor, exceptionHandler) {this.advice = buildAdvice(executor, exceptionHandler);this.pointcut = buildPointcut(asyncAnnotationTypes);
}protected Advice buildAdvice( executor, exceptionHandler) {AnnotationAsyncExecutionInterceptor interceptor = new AnnotationAsyncExecutionInterceptor(null);// 设置线程池和异常处理器interceptor.configure(executor, exceptionHandler);return interceptor;
}
注意AnnotationAsyncExecutionInterceptor是一个MethodInterceptor接口的实现类:
// MethodInterceptor拦截器接口的invoke方法
public Object invoke(final MethodInvocation invocation) throws Throwable {//...
}
总之,在aware阶段为AsyncAnnotationBeanPostProcessor对象注入this.advisor一个AsyncAnnotationAdvisor类型的对象,该对象包含BeanFactory和advice属性;其中advice属性包含一个MethodInterceptor拦截器。
[2] BPP-After阶段:为Bean对象生成代理
在AsyncAnnotationBeanPostProcessor的初始化过程完成后,当IOC初始化单例Bean对象时,会在初始化的后置处理器阶段调用AsyncAnnotationBeanPostProcessor的postProcessAfterInitialization方法:
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {// this.advisor在AsyncAnnotationBeanPostProcessor构建阶段已设值if (this.advisor == null || bean instanceof AopInfrastructureBean) {return bean;}// 是否已进行了AOP代理if (bean instanceof Advised) {Advised advised = (Advised) bean;if (!advised.isFrozen() && isEligible(AopUtils.getTargetClass(bean))) {// Add our local Advisor to the existing proxy's Advisor chain...if (this.beforeExistingAdvisors) {advised.addAdvisor(0, this.advisor);}else {advised.addAdvisor(this.advisor);}return bean;}}// 类中是否注解了@Asyncif (isEligible(bean, beanName)) {// 将bean对象存在了targetSource属性中,以便后续取用ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName);if (!proxyFactory.isProxyTargetClass()) {evaluateProxyInterfaces(bean.getClass(), proxyFactory);}proxyFactory.addAdvisor(this.advisor);customizeProxyFactory(proxyFactory);return proxyFactory.getProxy(getProxyClassLoader());}return bean;
}
如上述代码所示:
已完成AOP代理的不需要再次代理,将AsyncAnnotationBeanPostProcessor的this.advisor属性
添加到代理对象的List<Advisor>列表
中;未经过代理且类中注解了@Async的对象,Spring为其创建一个代理对象,并将this.advisor属性
信息保存到代理对象中。【代理过程参考:Spring系列-8 AOP原理】
【3】接口调用
Spring提供了两种代理方式:JDK动态代理和CGLIB代理,由于底层代理细节对实现原理并无影响,这里以JDK动态代理为例进行介绍。
被代理的对象在方法被调用时,进入JdkDynamicAopProxy的invoke拦截方法,注意每个代理对象对应一个JdkDynamicAopProxy对象。
[插图:解释ProxyFactory、ProxyConfig、Advised的关系,以及JdkDynamicAopProxy的传参路径]
涉及代码如下所示(保留主线逻辑):
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// hash/equals/toString check...TargetSource targetSource = this.advised.targetSource;Object target = targetSource.getTarget();Class<?> targetClass = (target != null ? target.getClass() : null);// 根据方法和字节码对象获取调用链,如果该方法未被@Async注解,则返回的集合为空;否则返回一个包含AnnotationAsyncExecutionInterceptor对象的单元素集合。List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);Object retVal;if (chain.isEmpty()) {Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);// 直接用过反射调用目标方法retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);} else {MethodInvocation invocation =new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);retVal = invocation.proceed();}// 返回结果类型校验...return retVal;
}
invoke方法的主线逻辑:
[1] 从targetSource属性中取出被代理的对象;
[2] 从 advised属性中获取拦截器链;
[3] 判断拦截器链是否为空,为空则直接通过反射调用该方法;否则进入拦截器对象;
[4] 进行结果类型的校验,并返回结果对象。
ReflectiveMethodInvocation介绍:
ReflectiveMethodInvocation是对调用链进行的一层封装:
public Object proceed() throws Throwable {// We start with an index of -1 and increment early.if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {return invokeJoinpoint();}Object interceptorOrInterceptionAdvice =this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
interceptorsAndDynamicMethodMatchers为构建ReflectiveMethodInvocation对象时传入的chain参数;即依次调用拦截器的拦截invoke方法,最后执行invokeJoinpoint()方法。
注意:调用拦截器时将this作为入参(该ReflectiveMethodInvocation对象),拦截器中可通过调用该对象的proceed()实现回调。
2.3 AnnotationAsyncExecutionInterceptor介绍:
当异步方法被调用时,堆栈逻辑进入到AnnotationAsyncExecutionInterceptor的invoke方法:
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);// 1.确定待执行的任务的线程池AsyncTaskExecutor executor = determineAsyncExecutor(userDeclaredMethod);// 2.封装执行逻辑成一个任务Callable<Object> task = () -> {try {// 回调ReflectiveMethodInvocation的proceed方法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;};// 3.将任务提交给线程池return doSubmit(task, executor, invocation.getMethod().getReturnType());
}
AnnotationAsyncExecutionInterceptor的invoke方法主线逻辑如下:
[1] 获取待执行的任务的线程池;
[2] 回调ReflectiveMethodInvocation的逻辑(将invocation.proceed())
封装为一个任务;
[3] 将任务提交给线程池执行。
当提交给线程池的任务被执行时,即invocation.proceed()
执行时进入到ReflectiveMethodInvocation的invokeJoinpoint()方法
,反射调用目标方法。
获取执行@Async方法的线程池:
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;
}
其中,this.executors对象用于缓存方法与线程池的关系,减少查询次数。
主线逻辑为:
如果@Async注解的value有值,则根据该值从beanFactory中获取对应的线程池Bean对象;否则获取默认的线程池,获取步骤如下:
protected Executor getDefaultExecutor(@Nullable BeanFactory beanFactory) {Executor defaultExecutor = super.getDefaultExecutor(beanFactory);return (defaultExecutor != null ? defaultExecutor : new SimpleAsyncTaskExecutor());
}
逻辑较为简单:通过父类的getDefaultExecutor方法获取默认的线程池,获取失败则使用SimpleAsyncTaskExecutor线程池对象。
父类getDefaultExecutor方法逻辑如下:
protected Executor getDefaultExecutor(@Nullable BeanFactory beanFactory) {if (beanFactory != null) {try {return beanFactory.getBean(TaskExecutor.class);} catch (NoUniqueBeanDefinitionException ex) {return beanFactory.getBean("taskExecutor", Executor.class);} catch (NoSuchBeanDefinitionException ex) {return beanFactory.getBean("taskExecutor", Executor.class);}}return null;
}
先尝试根据TaskExecutor类型从IOC中获取Bean对象,获取失败再次根据"taskExecutor"名称获取Executor类型的Bean对象,都获取失败时返回null;当从父类的getDefaultExecutor方法获取线程池结果为空时,会使用 SimpleAsyncTaskExecutor线程池对象。
注意:
SimpleAsyncTaskExecutor对象每次执行任务都会创建一个新的线程(且没有最大线程数设置),当并发量较大时可能导致严重的性能问题;建议使用@Async的项目自定义beanName为 "taskExecutor"且类型为TaskExecutor的线程池。
2.4 代理类型:
Spring提供了两种动态代理类型:JDK动态代理和CGLIB动态代理(可参考:AVASE-14 静态代理与动态代理),并支持通过配置对其进行指定。
AopProxy工厂(DefaultAopProxyFactory)创建AopProxy对象的过程如下:
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {if (!NativeDetector.inNativeImage() &&(config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config))) {Class<?> targetClass = config.getTargetClass();if (targetClass == null) {throw new AopConfigException("TargetSource cannot determine target class: " +"Either an interface or a target is required for proxy creation.");}if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {return new JdkDynamicAopProxy(config);}return new ObjenesisCglibAopProxy(config);} else {return new JdkDynamicAopProxy(config);}
}
NativeDetector.inNativeImage()
和config.isOptimize()
在整个代理和方法调用过程(未涉及配置)取默认值,可直接忽略;且通过Class<?> targetClass = config.getTargetClass();
得到的对象为(已被实例化的)Bean对象的字节码类型,不可能为接口类型或空对象,可直接忽略。[框架代码不同于业务代码,需要考虑多种场景的适配]
因此,上述代码可以简化为:
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {if (config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {Class<?> targetClass = config.getTargetClass();if (Proxy.isProxyClass(targetClass)) {return new JdkDynamicAopProxy(config);}return new ObjenesisCglibAopProxy(config);} else {return new JdkDynamicAopProxy(config);}
}// 目标类是否继承了接口(除SpringProxy外)
private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {Class<?>[] ifcs = config.getProxiedInterfaces();return (ifcs.length == 0 || (ifcs.length == 1 && SpringProxy.class.isAssignableFrom(ifcs[0])));
}
结果比较清晰:
【1】如果目标对象已被JDK动态代理过,则选择JDK动态代理;
【2】如果ProxyTarget属性为true 或者没有实现接口,使用CGBLIB代理;否则使用JDK动态代理。
顺便提一下:JDK动态代理生成代理类的速度较快(相对CGLLIB快8倍),但是运行速度较慢(比CGLIB慢10倍)。
相关文章:
Spring系列-9 Async注解使用与原理
背景: 本文作为Spring系列的第九篇,介绍Async注解的使用、注意事项和实现原理,原理部分会结合Spring框架代码进行。 本文可以和Spring系列-8 AOP原理进行比较阅读 1.使用方式 Async一般注解在方法上,用于实现方法的异步…...
Python自动化测试实战篇(6)用PO分层模式及思想,优化unittest+ddt+yaml+request登录接口自动化测试
这些是之前的文章,里面有一些基础的知识点在前面由于前面已经有写过,所以这一篇就不再详细对之前的内容进行描述 Python自动化测试实战篇(1)读取xlsx中账户密码,unittest框架实现通过requests接口post登录网站请求&…...
Linux 进程:父子进程
目录一、了解子进程二、创建子进程1.创建子进程2.区分父子进程三、理解子进程四、创建子进程的意义进程就是运行中的应用程序,如果一个程序较为庞大,我们可以给这个程序创建多个进程,每个进程负责一部分代码的运行。 A进程如果创建了B进程&am…...
Unity 之 实现读取代码写进Word文档功能实现 -- 软著脚本生成工具
Unity 之 实现读取代码写进Word文档功能前言一,实现步骤1.1 逻辑梳理1.2 用到工具二,实现读写文件2.1 读取目录相关2.2 读写文件三,编辑器拓展3.1 编辑器拓展介绍3.2 实现界面可视化四,源码分享4.1 工具目录4.2 完整代码前言 之所…...
Typora图床配置:Typora + PicGo + 阿里云OSS
文章目录一、前景提要二、相关链接三、搭建步骤1. 购买阿里云对象存储OSS2. 对象存储OSS:创建Bucket3. 阿里云:添加OSS访问用户及权限4. 安装Typora5. 配置PicGo方法一:使用PicGo-Core (Command line)方法二:使用PicGo(app)6. 最后…...
二进制搭建以太坊2.0节点-2023最新详细版文档
文章目录 一、配置 JWT 认证二、部署执行节点geth2.1 下载geth二进制文件2.2 geth节点启动三、部署共识节点Prysm3.1 下载Prysm脚本3.2 Prysm容器生成四、检查节点是否同步完成4.1 检查geth执行节点4.2 检查prysm共识节点4.3 geth常用命令五、节点同步详细说明5.1 启动时日志5.…...
如何简化跨网络安全域的文件发送流程,大幅降低IT人员工作量?
为什么要做安全域的隔离? 随着企业数字化转型的逐步深入,企业投入了大量资源进行信息系统建设,信息化程度日益提升。在这一过程中,企业也越来越重视核心数据资产的保护,数据资产的安全防护成为企业面临的重大挑战。 …...
带你深入了解c语言指针后续
前言 🎈个人主页:🎈 :✨✨✨初阶牛✨✨✨ 🐻推荐专栏: 🍔🍟🌯 c语言进阶 🔑个人信条: 🌵知行合一 🍉本篇简介:>:介绍c语言中有关指针更深层的知识. 金句分享: ✨在该…...
借助Intune无感知开启Bitlocker
希望使用 Intune 部署 BitLocker,但不知道从哪里开始?这是人们最开始使用 Intune 时最常见的问题之一。在本博客中,你将了解有关使用 Intune 管理 BitLocker 的所有信息,包括建议的设置、BitLocker CSP 在客户端上的工作方式&…...
零基础该如何转行Python工程师?学习路线是什么?
最近1年的主要学习时间,都投资到了 python 数据分析和数据挖掘上面来了,虽然经验并不是十分丰富,但希望也能把自己的经验分享下,最近也好多朋友给我留言,和我聊天,问我python该如何学习,才能少走…...
Go项目(商品微服务-1)
文章目录简介建表protohandler商品小结简介 商品微服务主要在于表的设计,建哪些表?表之间的关系是怎样的? 主要代码就是 CURD表和字段的设计是一个比较有挑战性的工作,比较难说清楚,也需要经验的积累,这里…...
机器学习——集成学习
引言 集成学习:让机器学习效果更好,单个不行,群殴走起。 分类 1. Bagging:训练多个分类器取平均(m代表树的个数)。 2.Boosting(提升算法):从弱学习器开始加,通过加权来进行训练。…...
VS编译系统 实用调试技巧
目录什么是bug?调试是什么?有多重要?debug和release的介绍windows环境调试介绍、一些调试实例如何写出(易于调试)的代码编程常见的错误什么是bug?其实bug在英文翻译中有表示臭虫的含义,因为第一次被发现的导致计算机…...
【华为OD机试模拟题】用 C++ 实现 - GPU 调度(2023.Q1)
最近更新的博客 【华为OD机试模拟题】用 C++ 实现 - 去重求和(2023.Q1) 文章目录 最近更新的博客使用说明GPU 调度题目输入输出示例一输入输出说明示例二输入输出说明Code使用说明 参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出,通过率才会高。...
腾讯前端必会react面试题合集
React-Router的路由有几种模式? React-Router 支持使用 hash(对应 HashRouter)和 browser(对应 BrowserRouter) 两种路由规则, react-router-dom 提供了 BrowserRouter 和 HashRouter 两个组件来实现应用的…...
Linux搭建SVN服务器,并内网穿透实现公网远程访问
文章目录1. Ubuntu安装SVN服务2. 修改配置文件2.1 修改svnserve.conf文件2.2 修改passwd文件2.3 修改authz文件3. 启动svn服务4. 内网穿透4.1 安装cpolar内网穿透4.2 创建隧道映射本地端口5. 测试公网访问6. 配置固定公网TCP端口地址6.1 保留一个固定的公网TCP端口地址6.2 配置…...
C++STL之list的模拟实现
目录 一.list准备 二. iterator迭代器 1._list_iterator 2.begin()、end() 3.const_begin()、const_end() 4.!&& 5. && -- 6.operator* 7.operator-> 三.Modify(修改) 1.insert() 2.erase() 3.push_back() && push_front() 4.pop_bac…...
为什么硬件性能监控很重要
当今的混合网络环境平衡了分布式网络和现代技术的实施。但它们并不缺少一个核心组件:服务器。保持网络正常运行时间归结为监控和管理导致网络停机的因素。极有可能导致性能异常的此类因素之一是硬件。使用硬件监控器监控网络硬件已成为一项关键需求。 硬件监视器是…...
HTTP缓存
HTTP缓存HTTP缓存引发的一个问题HTTP缓存的作用HTTP缓存的分类强制缓存协商缓存(解决强缓存下资源不更新问题)缓存策略HTTP缓存引发的一个问题 有一次在开发移动端H5项目,UI提了几个UI问题,经过样式调试,android上没有…...
SPI设备树处理过程
SPI设备树处理过程 文章目录SPI设备树处理过程参考资料:一、 spi_device结构体二、 SPI设备树格式2.1 SPI Master2.2 SPI Device2.3 设备树示例三、设备树实例3.1 使用GPIO模拟的SPI控制器3.2 IMX6ULL SPI控制器四、 设备树处理过程致谢参考资料: 内核头…...
数据有哪些重要的作用?
我们正处在科技高速发展的时代,如今互联网已经与我们的生活息息相关,我们每天在互联网产生大量的数据,这些数据散落在网络中看似没有怎么作用,但是这些数据经过系统的处理整合起来确实非常有价值的。 一、 发展大数据技术可以提高…...
spring面试题总结
1、spring是什么? spring是一个轻量级IOC和AOP容器框架,是为Java应用程序提供基础性服务的一套框架,目的是用于简化企业应用的开发,开发者只需要关注业务需求即可: core container 容器组件 spring context,…...
使用MUI与H5+构建移动端app
前言 通过mui构建APP 效果图: <!DOCTYPE html> <html> <head><meta charset...
第17篇:Java变量总结
目录 1.变量的概念 1.1 变量来源 1.2 计算机中的变量 1.3 变量如何在内存中存储 2.Java变量...
使用51单片机的GPIO输出占空比可调节的PWM波
一、前言 在一些单片机或微控制器中,通用GPIO可以被配置为产生PWM信号。PWM即脉冲宽度调制,是一种用于模拟输出的技术。它可以通过改变输出信号的脉冲宽度来控制电路中的电平,从而实现对电路的控制。 二、什么是PWM波? PWM波&a…...
从产品经理的角度如何提升项目的交付质量?
提高交付质量 ,对于每个IT公司都是永恒的话题。 交付质量其实包含2重意义, 一是交付的高质量(客户角度),即客户的满意度;二是高质量的交付(交付团队的角度),这里是指如何…...
JavaScript BOM【快速掌握知识点】
目录 Window对象的常用属性 语法: Window对象的常用方法 语法: open()和close()方法 History对象 常用属性和方法 示例 Location对象 常用属性 常用方法 Document对象的常用方法 定时函数 超时调用:setTimeout() 间歇调用&…...
【算法】哈希表
作者:指针不指南吗 专栏:算法篇 🐾或许会很慢,但是不可以停下来🐾 文章目录1.定义2.优点3.数字哈希3.1拉链法3.2开放寻址法3.3 例题4.字符串哈希1.定义 哈希表(Hash table),是根据键…...
彻底搞懂React-hook链表构建原理
写在前面的小结 每一个 hook 函数都有对应的 hook 对象保存状态信息useContext是唯一一个不需要添加到 hook 链表的 hook 函数只有 useEffect、useLayoutEffect 以及 useImperativeHandle 这三个 hook 具有副作用,在 render 阶段需要给函数组件 fiber 添加对应的副…...
【数据挖掘实战】——应用系统负载分析与容量预测(ARIMA模型)
项目地址:Datamining_project: 数据挖掘实战项目代码 目录 一、背景和挖掘目标 1、问题背景 2、传统方法的不足 2、原始数据 3、挖掘目标 二、分析方法与过程 1、初步分析 2、总体流程 第一步:数据抽取 第二步:探索分析 第三步&a…...
专业网站设计建站/西安做网站的网络公司
不管是我们为自己的网站做SEO优化,还是做公司网站的SEO优化,都建议大家能在SEO工作开始之前做好一份详细的SEO方案,不要怕费时间,只有有计划地去工作,才能让效率大大地提高不管是我们为自己的网站做SEO优化,…...
论网站建设情况/百度怎么投放广告
一、基本原理 1.数据源无关的数据统一操作,LINQ语句做了一个程序语句到各种数据源之间的抽象统一中间件。where开始,select或group结束. order 和group,select new等各种语句 。 2.用了拓展方法,在generic枚举类型来做࿰…...
遵义网站建设厂家/竞价系统
<wbr> 1、什么是const?<br> 常类型是指使用类型修饰符const说明的类型,常类型的变量或对象的值是不能被更新的。(当然,我们可以偷梁换柱进行更新:)</wbr> 2、为什么引入const? cons…...
中山学校的网站建设/常用于网站推广的营销手段是
首先是区别: <>先去系统目录中找头文件,如果没有在到当前目录下找。所以像标准的头文件 stdio.h、stdlib.h等用这个方法。 而""首先在当前目录下寻找,如果找不到,再到系统目录中寻找。 这个用于include自定义的头…...
什么网站能找到做展览的工人/天津百度推广开户
代码格式对齐: CtrlA,全选 CtrlK ,CtrlF ,对齐 CTRL M, CTRL O 折叠所有方法 CTRL M, CTRL L展开所有方法...
济南商城网站建设/站长号
本文介绍c里面的四个智能指针: auto_ptr, shared_ptr, weak_ptr, unique_ptr 其中后三个是c11支持,并且第一个已经被c11弃用。为什么要使用智能指针:我们知道c的内存管理是让很多人头疼的事,当我们写一个new语句时,一般就会立即把…...