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

Spring 在多线程环境下如何确保事务一致性

  • 问题在现

  • 如何解决异步执行

  • 多线程环境下如何确保事务一致性

  • 事务王国回顾

  • 事务实现方式回顾

  • 编程式事务

  • 利用编程式事务解决问题

  • 问题分析完了,那么如何解决问题呢?

  • 小结


问题在现

我先把问题抛出来,大家就明白本文目的在于解决什么样的业务痛点了:

public void removeAuthorityModuleSeq(Integer authorityModuleId, IAuthorityService iAuthorityService, IRoleAuthorityService iRoleAuthorityService) {//1.查询出当前资源模块下所有资源,查询出来后进行删除deleteAuthoritiesOfCurrentAuthorityModule(authorityModuleId, iAuthorityService, iRoleAuthorityService);//2.查询出当前资源模块下所有子模块,递归查询,当删除完所有子模块下的资源后,再删除所有子模块,最终删除当前资源模块deleteSonAuthorityModuleUnderCurrentAuthorityModule(authorityModuleId, iAuthorityService, iRoleAuthorityService);//3.删除当前资源模块removeById(authorityModuleId);
}

如果我希望将步骤1和步骤2并行执行,然后确保步骤1和步骤2执行成功后,再执行步骤3,等到步骤3执行完毕后,再提交全部事务,这个需求该如何实现呢?

如何解决异步执行

上面需求第一点是: 如何让任务异步并行执行,如何实现二元依赖呢?

说到异步执行,很多小伙伴首先想到Spring中提供的@Async注解,但是Spring提供的异步执行任务能力并不足以解决我们当前的需求。

@Async注解原理简单来说,就是扫描IOC中的bean,给方法上标注有@Async注解的bean进行代理,代理的核心是添加一个MethodInterceptorAsyncExecutionInterceptor,该方法拦截器负责将方法真正的执行包装为任务,放入线程池中执行。

下面我们先使用CompletableFuture来完成我们第一步需求:

public void removeAuthorityModuleSeq(Integer authorityModuleId, IAuthorityService iAuthorityService, IRoleAuthorityService iRoleAuthorityService) {CompletableFuture.runAsync(()->{//两个并行执行的任务CompletableFuture<Void> future1 = CompletableFuture.runAsync(() ->deleteAuthoritiesOfCurrentAuthorityModule(authorityModuleId, iAuthorityService, iRoleAuthorityService),executor);CompletableFuture<Void> future2 = CompletableFuture.runAsync(() ->deleteSonAuthorityModuleUnderCurrentAuthorityModule(authorityModuleId, iAuthorityService, iRoleAuthorityService), executor);//等待两个并行任务执行完后,再执行最后一个步骤CompletableFuture.allOf(future1,future2).thenRun(()->removeById(authorityModuleId)); },executor);
}

多线程环境下如何确保事务一致性

我们已经完成了任务的异步执行化,那么又如何确保多线程环境下的事务一致性问题呢?

public void removeAuthorityModuleSeq(Integer authorityModuleId, IAuthorityService iAuthorityService, IRoleAuthorityService iRoleAuthorityService) {CompletableFuture.runAsync(()->{//两个并行执行的任务CompletableFuture<Void> future1 = CompletableFuture.runAsync(() ->deleteAuthoritiesOfCurrentAuthorityModule(authorityModuleId, iAuthorityService, iRoleAuthorityService),executor);CompletableFuture<Void> future2 = CompletableFuture.runAsync(() ->deleteSonAuthorityModuleUnderCurrentAuthorityModule(authorityModuleId, iAuthorityService, iRoleAuthorityService), executor);//等待两个并行任务执行完后,再执行最后一个步骤CompletableFuture.allOf(future1,future2).thenRun(()->removeById(authorityModuleId));},executor);
}

在Spring环境下说到事务控制,大家第一反应就想到使用@Transactional注解解决问题,但是这里显然行不通,为什么行不通呢?

我还是简单的对Spring事务实现原理进行一番概括:

事务王国回顾

事务管理大体分为三个流程:事务创建 ,事务执行,事务结束

事务创建涉及到一些属性的配置,如:

  • 事务的隔离级别

  • 事务的传播行为

  • 事务的超时时间

  • 是否为只读事务

由于涉及属性颇多,并且后期还有可能进行扩展,因此必须通过一个类来封装这些属性,在Spring中对应TransactionDefinition

有了事务相关属性定义后,我们就可以利用TransactionDefinition来创建一个事务了,在Spring中局部事务由PlatformTransactionManager负责管理,创建事务也是由PlatformTransactionManager负责提供:

 TransactionStatus getTransaction(@Nullable TransactionDefinition definition)throws TransactionException;

如果我们希望追踪事务的状态,例如: 事务已完成,事务回滚等,那么就需要一个事务状态类贯穿当前事务的执行流程,在Spring中由TransactionStatus负责完成。

对于常见的数据源而言,通常需要记录的事务状态有如下几点:

  • 当前事务是否是新事务

  • 当前事务是否结束

  • 当前事务是否需要回滚(通过标记来判断,因此我也可以在业务流程中手动设置标记为true,来让事务在没有发生异常的情况下进行回滚)

  • 当前事务是否设置了回滚点(savePoint)

事务的执行过程就是具体业务代码的执行流程,这里就不多说了。

事务的结束分为两种情况: 需要进行事务回滚或者事务正常提交,如果是事务回滚,还需要判断TransactionStatus 中的savePoint是否被设置了。

事务实现方式回顾

Spring中常见的事务实现方式有两种: 编程式和声明式。

编程式事务使用是本文重点,因此这里按下不表,我们先来复习一下声明式事务的使用

声明式事务就是使用我们常见的@Transactional注解完成的,声明式事务优点就在于让事务代码与业务代码解耦,通过Spring中提供的声明式事务使用,我们也可以发觉我们只需要编写业务代码即可,而事务的管理基本不需要我们操心,Spring就像使用了魔法一样,帮我们自动完成了。

之所以那么神奇,本质还是依靠Spring框架提供的Bean生命周期相关回调接口和AOP结合完成的,简述如下:

  • 通过自动代理创建器依次尝试为每个放入容器中的bean尝试进行代理

  • 尝试进行代理的过程对于事务管理来说,就是利用事务管理涉及到的增强器advisor,即TransactionAttributeSourceAdvisor

  • 判断当前增强器是否能够应用与当前bean上,怎么判断呢?—> advisor内部的pointCut喽 !

  • 如果能够应用,那么好,为当前bean创建代理对象返回,并且往代理对象内部添加一个TransactionInterceptor拦截器。

  • 此时我们再从容器中获取,拿到的就是代理对象了,当我们调用代理对象的方法时,首先要经过代理对象内部拦截器链的处理,处理完后,最终才会调用被代理对象的方法。(这里其实就是责任链模式的应用)

对于被事务增强器TransactionAttributeSourceAdvisor代理的bean而言,代理对象内部会存在一个TransactionInterceptor,该拦截器内部构造了一个事务执行的模板流程:

protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,final InvocationCallback invocation) throws Throwable {//TransactionAttributeSource内部保存着当前类某个方法对应的TransactionAttribute---事务属性源//可以看做是一个存放TransactionAttribute与method方法映射的池子TransactionAttributeSource tas = getTransactionAttributeSource();//获取当前事务方法对应的TransactionAttributefinal TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);//定位TransactionManagerfinal TransactionManager tm = determineTransactionManager(txAttr);.....//类型转换为局部事务管理器PlatformTransactionManager ptm = asPlatformTransactionManager(tm);final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {//TransactionManager根据TransactionAttribute创建事务后返回//TransactionInfo封装了当前事务的信息--包括TransactionStatusTransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);Object retVal;try {//继续执行过滤器链---过滤链最终会调用目标方法//因此可以理解为这里是调用目标方法retVal = invocation.proceedWithInvocation();}catch (Throwable ex) {//目标方法抛出异常则进行判断是否需要回滚completeTransactionAfterThrowing(txInfo, ex);throw ex;}finally {//清除当前事务信息cleanupTransactionInfo(txInfo);}...//正常返回,那么就正常提交事务呗(当然还是需要判断TransactionStatus状态先)commitTransactionAfterReturning(txInfo);return retVal;}...

编程式事务

还记得本文一开始提出的业务需求吗?

不清楚,可以回看一下,在上文,我们已经解决了任务异步并行执行的难题,下面我们需要解决的就是如何确保Spring在多线程环境下也能保持事务一致性。

通过上文对Spring事务基础和声明式事务的原理回顾,相信大家也发现了,声明式事务并不能解决我们当前的问题,那么就只能求助于编程式事务了。

那么编程式事务是什么样子呢?

其实上面TransactionInterceptor给出的那套模板流程,就是编程式事务使用的模范案例,我们可以简化上面的模板流程,简单使用如下:

public class TransactionMain {public static void main(String[] args) throws ClassNotFoundException, SQLException {test();}private static void test() {DataSource dataSource = getDS();JdbcTransactionManager jtm = new JdbcTransactionManager(dataSource);//JdbcTransactionManager根据TransactionDefinition信息来进行一些连接属性的设置//包括隔离级别和传播行为等DefaultTransactionDefinition transactionDef = new DefaultTransactionDefinition();//开启一个新事务---此时autocommit已经被设置为了false,并且当前没有事务,这里创建的是一个新事务TransactionStatus ts = jtm.getTransaction(transactionDef);//进行业务逻辑操作try {update(dataSource);jtm.commit(ts);}catch (Exception e){jtm.rollback(ts);System.out.println("发生异常,我已回滚");}}private static void update(DataSource dataSource) throws Exception {JdbcTemplate jt = new JdbcTemplate();jt.setDataSource(dataSource);jt.update("UPDATE Department SET Dname=\"大忽悠\" WHERE id=6");throw new Exception("我是来捣乱的");}
}

利用编程式事务解决问题

我们明白了编程式事务的使用,相信大家也都知道问题如何解决了,下面我给出一份看似正确的解决方案:

package com.user.util;import lombok.RequiredArgsConstructor;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;/*** 多线程事务一致性管理 <br>* 声明式事务管理无法完成,此时我们只能采用初期的编程式事务管理才行* @author 大忽悠* @create 2022/10/19 21:34*/
@Component
@RequiredArgsConstructor
public class MultiplyThreadTransactionManager {/*** 如果是多数据源的情况下,需要指定具体是哪一个数据源*/private final DataSource dataSource;/*** 执行的是无返回值的任务* @param tasks 异步执行的任务列表* @param executor 异步执行任务需要用到的线程池,考虑到线程池需要隔离,这里强制要求传*/public void runAsyncButWaitUntilAllDown(List<Runnable> tasks, Executor executor) {if(executor==null){throw new IllegalArgumentException("线程池不能为空");}DataSourceTransactionManager transactionManager = getTransactionManager();//是否发生了异常AtomicBoolean ex=new AtomicBoolean();List<CompletableFuture> taskFutureList=new ArrayList<>(tasks.size());List<TransactionStatus> transactionStatusList=new ArrayList<>(tasks.size());tasks.forEach(task->{taskFutureList.add(CompletableFuture.runAsync(() -> {try{//1.开启新事务transactionStatusList.add(openNewTransaction(transactionManager));//2.异步任务执行task.run();}catch (Throwable throwable){//打印异常throwable.printStackTrace();//其中某个异步任务执行出现了异常,进行标记ex.set(Boolean.TRUE);//其他任务还没执行的不需要执行了taskFutureList.forEach(completableFuture -> completableFuture.cancel(true));}}, executor));});try {//阻塞直到所有任务全部执行结束---如果有任务被取消,这里会抛出异常滴,需要捕获CompletableFuture.allOf(taskFutureList.toArray(new CompletableFuture[]{})).get();} catch (InterruptedException | ExecutionException e) {e.printStackTrace();}//发生了异常则进行回滚操作,否则提交if(ex.get()){System.out.println("发生异常,全部事务回滚");transactionStatusList.forEach(transactionManager::rollback);}else {System.out.println("全部事务正常提交");transactionStatusList.forEach(transactionManager::commit);}}private TransactionStatus openNewTransaction(DataSourceTransactionManager transactionManager) {//JdbcTransactionManager根据TransactionDefinition信息来进行一些连接属性的设置//包括隔离级别和传播行为等DefaultTransactionDefinition transactionDef = new DefaultTransactionDefinition();//开启一个新事务---此时autocommit已经被设置为了false,并且当前没有事务,这里创建的是一个新事务return transactionManager.getTransaction(transactionDef);}private DataSourceTransactionManager getTransactionManager() {return new DataSourceTransactionManager(dataSource);}
}

大家思考上面的代码存在问题吗?

测试:

public void test(){List<Runnable> tasks=new ArrayList<>();tasks.add(()->{userMapper.deleteById(26);});tasks.add(()->{signMapper.deleteById(10);});multiplyThreadTransactionManager.runAsyncButWaitUntilAllDown(tasks, Executors.newCachedThreadPool());
}

任务正常都执行完毕,事务进行提交,但是会抛出异常,导致事务回滚:

图片

抓关键字:

No value for key [HikariDataSource (HikariPool-1)] bound to thread [main]
解释: 无法在当前线程绑定的threadLocal中寻找到HikariDataSource作为key,对应关联的资源对象ConnectionHolder

这里需要再次回顾一下Spring事务实现的小细节:

一次事务的完成通常都是默认在当前线程内完成的,又因为一次事务的执行过程中,涉及到对当前数据库连接Connection的操作,因此为了避免将Connection在事务执行过程中来回传递,我们可以将Connextion绑定到当前事务执行线程对应的ThreadLocalMap内部,顺便还可以将一些其他属性也放入其中进行保存,在Spring中,负责保存这些ThreadLocal属性的实现类由TransactionSynchronizationManager承担。

TransactionSynchronizationManager类内部默认提供了下面六个ThreadLocal属性,分别保存当前线程对应的不同事务资源:

   //保存当前事务关联的资源--默认只会在新建事务的时候保存当前获取到的DataSource和当前事务对应Connection的映射关系--当然这里Connection被包装为了ConnectionHolderprivate static final ThreadLocal<Map<Object, Object>> resources =new NamedThreadLocal<>("Transactional resources");//事务监听者--在事务执行到某个阶段的过程中,会去回调监听者对应的回调接口(典型观察者模式的应用)---默认为空集合private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =new NamedThreadLocal<>("Transaction synchronizations");//见名知意: 存放当前事务名字private static final ThreadLocal<String> currentTransactionName =new NamedThreadLocal<>("Current transaction name");//见名知意: 存放当前事务是否是只读事务private static final ThreadLocal<Boolean> currentTransactionReadOnly =new NamedThreadLocal<>("Current transaction read-only status");//见名知意: 存放当前事务的隔离级别private static final ThreadLocal<Integer> currentTransactionIsolationLevel =new NamedThreadLocal<>("Current transaction isolation level");//见名知意: 存放当前事务是否处于激活状态private static final ThreadLocal<Boolean> actualTransactionActive =new NamedThreadLocal<>("Actual transaction active");

那么上面抛出的异常的原因也就很清楚了,无法在main线程找到当前事务对应的资源,原因如下:

图片

开启新事务时,事务相关资源都被绑定到了thread-cache-pool-1线程对应的threadLocalMap内部,而当执行事务提交代码时,commit内部需要从TransactionSynchronizationManager中获取当前事务的资源,显然我们无法从main线程对应的threadLocalMap中获取到对应的事务资源,这也就是异常抛出的原因。

问题分析完了,那么如何解决问题呢?

这里给出一个我首先想到的简单粗暴的方法—CopyTransactionResource—将事务资源在两个线程间来回复制

这里给出解决后问题后的代码示例:

package com.user.util;import lombok.Builder;
import lombok.RequiredArgsConstructor;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import javax.sql.DataSource;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;/*** 多线程事务一致性管理 <br>* 声明式事务管理无法完成,此时我们只能采用初期的编程式事务管理才行* @author 大忽悠* @create 2022/10/19 21:34*/
@Component
@RequiredArgsConstructor
public class MultiplyThreadTransactionManager {/*** 如果是多数据源的情况下,需要指定具体是哪一个数据源*/private final DataSource dataSource;/*** 执行的是无返回值的任务* @param tasks 异步执行的任务列表* @param executor 异步执行任务需要用到的线程池,考虑到线程池需要隔离,这里强制要求传*/public void runAsyncButWaitUntilAllDown(List<Runnable> tasks, Executor executor) {if(executor==null){throw new IllegalArgumentException("线程池不能为空");}DataSourceTransactionManager transactionManager = getTransactionManager();//是否发生了异常AtomicBoolean ex=new AtomicBoolean();List<CompletableFuture> taskFutureList=new ArrayList<>(tasks.size());List<TransactionStatus> transactionStatusList=new ArrayList<>(tasks.size());List<TransactionResource> transactionResources=new ArrayList<>(tasks.size());tasks.forEach(task->{taskFutureList.add(CompletableFuture.runAsync(() -> {try{//1.开启新事务transactionStatusList.add(openNewTransaction(transactionManager));//2.copy事务资源transactionResources.add(TransactionResource.copyTransactionResource());//3.异步任务执行task.run();}catch (Throwable throwable){//打印异常throwable.printStackTrace();//其中某个异步任务执行出现了异常,进行标记ex.set(Boolean.TRUE);//其他任务还没执行的不需要执行了taskFutureList.forEach(completableFuture -> completableFuture.cancel(true));}}, executor));});try {//阻塞直到所有任务全部执行结束---如果有任务被取消,这里会抛出异常滴,需要捕获CompletableFuture.allOf(taskFutureList.toArray(new CompletableFuture[]{})).get();} catch (InterruptedException | ExecutionException e) {e.printStackTrace();}//发生了异常则进行回滚操作,否则提交if(ex.get()){System.out.println("发生异常,全部事务回滚");for (int i = 0; i < tasks.size(); i++) {transactionResources.get(i).autoWiredTransactionResource();transactionManager.rollback(transactionStatusList.get(i));transactionResources.get(i).removeTransactionResource();}}else {System.out.println("全部事务正常提交");for (int i = 0; i < tasks.size(); i++) {transactionResources.get(i).autoWiredTransactionResource();transactionManager.commit(transactionStatusList.get(i));transactionResources.get(i).removeTransactionResource();}}}private TransactionStatus openNewTransaction(DataSourceTransactionManager transactionManager) {//JdbcTransactionManager根据TransactionDefinition信息来进行一些连接属性的设置//包括隔离级别和传播行为等DefaultTransactionDefinition transactionDef = new DefaultTransactionDefinition();//开启一个新事务---此时autocommit已经被设置为了false,并且当前没有事务,这里创建的是一个新事务return transactionManager.getTransaction(transactionDef);}private DataSourceTransactionManager getTransactionManager() {return new DataSourceTransactionManager(dataSource);}/*** 保存当前事务资源,用于线程间的事务资源COPY操作*/@Builderprivate static class TransactionResource{//事务结束后默认会移除集合中的DataSource作为key关联的资源记录private  Map<Object, Object> resources = new HashMap<>();//下面五个属性会在事务结束后被自动清理,无需我们手动清理private  Set<TransactionSynchronization> synchronizations =new HashSet<>();private  String currentTransactionName;private Boolean currentTransactionReadOnly;private Integer currentTransactionIsolationLevel;private Boolean actualTransactionActive;public static TransactionResource copyTransactionResource(){return TransactionResource.builder()//返回的是不可变集合.resources(TransactionSynchronizationManager.getResourceMap())//如果需要注册事务监听者,这里记得修改--我们这里不需要,就采用默认负责--spring事务内部默认也是这个值.synchronizations(new LinkedHashSet<>()).currentTransactionName(TransactionSynchronizationManager.getCurrentTransactionName()).currentTransactionReadOnly(TransactionSynchronizationManager.isCurrentTransactionReadOnly()).currentTransactionIsolationLevel(TransactionSynchronizationManager.getCurrentTransactionIsolationLevel()).actualTransactionActive(TransactionSynchronizationManager.isActualTransactionActive()).build();}public void autoWiredTransactionResource(){resources.forEach(TransactionSynchronizationManager::bindResource);//如果需要注册事务监听者,这里记得修改--我们这里不需要,就采用默认负责--spring事务内部默认也是这个值TransactionSynchronizationManager.initSynchronization();TransactionSynchronizationManager.setActualTransactionActive(actualTransactionActive);TransactionSynchronizationManager.setCurrentTransactionName(currentTransactionName);TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(currentTransactionIsolationLevel);TransactionSynchronizationManager.setCurrentTransactionReadOnly(currentTransactionReadOnly);}public void removeTransactionResource() {//事务结束后默认会移除集合中的DataSource作为key关联的资源记录//DataSource如果重复移除,unbindResource时会因为不存在此key关联的事务资源而报错resources.keySet().forEach(key->{if(!(key instanceof  DataSource)){TransactionSynchronizationManager.unbindResource(key);}});}}
}

增加异常抛出,测试是否能够保证多线程间的事务一致性:

@SpringBootTest(classes = UserMain.class)
public class Test {@Resourceprivate UserMapper userMapper;@Resourceprivate SignMapper signMapper;@Resourceprivate MultiplyThreadTransactionManager multiplyThreadTransactionManager;@SneakyThrows@org.junit.jupiter.api.Testpublic void test(){List<Runnable> tasks=new ArrayList<>();tasks.add(()->{userMapper.deleteById(26);throw new RuntimeException("我就要抛出异常!");});tasks.add(()->{signMapper.deleteById(10);});multiplyThreadTransactionManager.runAsyncButWaitUntilAllDown(tasks, Executors.newCachedThreadPool());}}

图片

事务都进行了回滚,数据库数据没变

相关文章:

Spring 在多线程环境下如何确保事务一致性

问题在现 如何解决异步执行 多线程环境下如何确保事务一致性 事务王国回顾 事务实现方式回顾 编程式事务 利用编程式事务解决问题 问题分析完了&#xff0c;那么如何解决问题呢&#xff1f; 小结 问题在现 我先把问题抛出来&#xff0c;大家就明白本文目的在于解决什…...

[Machine Learning] Learning with Noisy Data

文章目录 Probabilistic Perspective of NoiseBias and VarianceRobustness among Surrogate Loss FunctionsNMF Probabilistic Perspective of Noise 假设数据来源于一个确定的函数&#xff0c;叠加了高斯噪声。我们有&#xff1a; y h ( x ) ϵ y h(x) \epsilon yh(x)ϵ…...

C++中有哪些常用的标准库?

C中有许多常用的标准库&#xff0c;这些库提供了丰富的功能和工具&#xff0c;方便开发人员进行各种任务。以下是一些常见的C标准库&#xff1a; iostream&#xff1a;用于输入和输出操作&#xff0c;包括cin、cout和cerr等类和函数。algorithm&#xff1a;提供了许多常用的算…...

软考-信息安全工程师概述

本文为作者学习文章&#xff0c;按作者习惯写成&#xff0c;如有错误或需要追加内容请留言&#xff08;不喜勿喷&#xff09; 本文为追加文章&#xff0c;后期慢慢追加 2023年10月 信息考试大纲 通过本考试的合格人员能够掌握网络信息安全的基础知识和技术原理&#xff0c;…...

2023-2024年华为ICT网络赛道模拟题库

2023-2024年网络赛道模拟题库上线啦&#xff0c;全面覆盖网络&#xff0c;安全&#xff0c;vlan考点&#xff0c;都是带有解析 参赛对象及要求&#xff1a; 参赛对象&#xff1a;现有华为ICT学院及未来有意愿成为华为ICT学院的本科及高职院校在校学生。 参赛要求&#xff1a…...

英特尔参与 CentOS Stream 项目

导读红帽官方发布公告欢迎英特尔参与进 CentOS Stream 项目&#xff0c;并表示 “这一举措不仅进一步深化了我们长期的合作关系&#xff0c;也构建在英特尔已经在 Fedora 项目中积极贡献的基础之上。” 目前&#xff0c;CentOS Stream 共包括以下特别兴趣小组&#xff08;SIG&a…...

Centos 服务器 MySQL 8.0 快速开启远程访问

环境&#xff1a; MySQL 8.0&#xff08;低版本会有些不同&#xff09;&#xff0c; Rocky Linux 9.0&#xff08;CentOS&#xff09; 直接上干货&#xff0c;相信大家看到这个文章的时候都已经安装完了。 1. 先从服务器上使用 root 进行登录&#xff08;刚安装完默认只能本地…...

充电保护芯片TP4054国产替代完全兼容DP4054DP4054H 锂电充电芯片

■产品概述 DP4054H是-款完整的采用恒定电流/恒定电压单节锂离子电池充电管理芯片。其SOT小封装和较少的外部元件数目使其成为便携式应用的理想器件&#xff0c;DP4054H可 以适合USB电源和适配器电源工作。 由于采用了内部PMOSFET架构&#xff0c;加上防倒充电路,所以不需要外…...

Java Spring Boot中的爬虫防护机制

随着互联网的发展&#xff0c;爬虫技术也日益成熟和普及。然而&#xff0c;对于某些网站来说&#xff0c;爬虫可能会成为一个问题&#xff0c;导致资源浪费和安全隐患。本文将介绍如何使用Java Spring Boot框架来防止爬虫的入侵&#xff0c;并提供一些常用的防护机制。 引言&a…...

状态模式 行为型模式之六

1.定义 允许一个对象在其对象内部状态改变时改变它的行为。 2.组成结构 Context&#xff1a;定义客户感兴趣的接口&#xff1b;维护一个ConcreteState子类的实例&#xff0c;这个实例定义当前的状态。State&#xff1a;定义一个接口来封装Context的与特定状态相关的行为。Co…...

JAVA NIO深入剖析

4.1 Java NIO 基本介绍 Java NIO(New IO)也有人称之为 java non-blocking IO是从Java 1.4版本开始引入的一个新的IO API,可以替代标准的Java IO API。NIO与原来的IO有同样的作用和目的,但是使用的方式完全不同,NIO支持面向缓冲区的、基于通道的IO操作。NIO将以更加高效的方…...

企业电子招投标系统源码之电子招投标系统建设的重点和未来趋势

功能描述 1、门户管理&#xff1a;所有用户可在门户页面查看所有的公告信息及相关的通知信息。主要板块包含&#xff1a;招标公告、非招标公告、系统通知、政策法规。 2、立项管理&#xff1a;企业用户可对需要采购的项目进行立项申请&#xff0c;并提交审批&#xff0c;查看所…...

基于正点原子alpha开发板的第三篇系统移植

系统移植的三大步骤如下&#xff1a; 系统uboot移植系统linux移植系统rootfs制作 一言难尽&#xff0c;踩了不少坑&#xff0c;当时只是想学习驱动开发&#xff0c;发现必须要将第三篇系统移植弄好才可以学习后面驱动&#xff0c;现将移植好的文件分享出来&#xff1a; 仓库&…...

数据结构与算法设计分析——贪心算法的应用

目录 一、贪心算法的定义二、贪心算法的基本步骤三、贪心算法的性质&#xff08;一&#xff09;最优子结构性质&#xff08;二&#xff09;贪心选择性质 四、贪心算法的应用&#xff08;一&#xff09;哈夫曼树——哈夫曼编码&#xff08;二&#xff09;图的应用——求最小生成…...

Leetcode 2895. Minimum Processing Time

Leetcode 2895. Minimum Processing Time 1. 解题思路2. 代码实现 题目链接&#xff1a;2895. Minimum Processing Time 1. 解题思路 这一题整体上来说其实没啥难度&#xff0c;就是一个greedy算法&#xff0c;只需要想明白耗时长的任务一定要优先执行&#xff0c;不存在某个…...

学信息系统项目管理师第4版系列21_范围管理

1. 产品范围 1.1. 某项产品、服务或成果所具有的特征和功能 1.2. 产品范围的完成情况是根据产品需求来衡量的 1.3. “需求”是指根据特定协议或其他强制性规范&#xff0c;产品、服务或成果必须具备的条件或能力 2. 项目范围 2.1. 包括产品范围&#xff0c;是为交付具有规…...

threejs 透明贴图,模型透明,白边

问题 使用Threejs加载模型时&#xff0c;模型出现了上面的问题。模型边缘部分白边&#xff0c;或者模型出现透明问题。 原因 出现这种问题是模型制作时使用了透明贴图。threejs无法直接处理贴图。 解决 场景一 模型有多个贴图时&#xff08;一个透贴和其他贴图&#xff0…...

CCF CSP认证 历年题目自练Day21

题目一 试题编号&#xff1a; 201909-1 试题名称&#xff1a; 小明种苹果 时间限制&#xff1a; 2.0s 内存限制&#xff1a; 512.0MB 题目分析&#xff08;个人理解&#xff09; 先看输入&#xff0c;第一行输入苹果的棵树n和每一次掉的苹果数m还是先如何存的问题&#xf…...

【Python_PySide2学习笔记(十六)】多行文本框QPlainTextEdit类的的基本用法

多行文本框QPlainTextEdit类的的基本用法 前言正文1、创建多行文本框2、多行文本框获取文本3、多行文本框获取选中文本4、多行文本框设置提示5、多行文本框设置文本6、多行文本框在末尾添加文本7、多行文本框在光标处插入文本8、多行文本框清空文本9、多行文本框拷贝文本到剪贴…...

linux上negix部署静态页面

1.看配置文件 进入cndf.d 这里的是配置部署项目中的文件 进入一个查看下 上面的是服务的域名&#xff0c;服务是http://test.fun-med.cn/#/&#xff0c;后面加服务名&#xff08;你的前端&#xff09; 2.看下页面位置 和上面的路径要匹配...

41.说说Promise自身的静态方法

41.说说Promise自身的静态方法 Promise.all &#xff08;有一个失败就失败&#xff0c;所有的都成功就成功&#xff09;Promise.race &#xff08;有一个成功就成功&#xff0c;有一个失败就失败&#xff09;Promise.allSettled &#xff08;所有的异步操作执行完毕之后&#…...

通讯网关软件019——利用CommGate X2OPCUA实现OPC UA访问Oracle服务器

本文介绍利用CommGate X2OPCUA实现OPC UA访问ORACLE数据库。CommGate X2OPCUA是宁波科安网信开发的网关软件&#xff0c;软件可以登录到网信智汇(http://wangxinzhihui.com)下载。 【案例】如下图所示&#xff0c;实现上位机通过OPC UA来获取ORACLE数据库的数据。 【解决方案】…...

【机器学习】svm

参考 sklearn中SVC中的参数说明与常用函数_sklearn svc参数-CSDN博客https://blog.csdn.net/transformed/article/details/90437821 参考PYthon 教你怎么选择SVM的核函数kernel及案例分析_clfsvm.svc(kernel)-CSDN博客https://blog.csdn.net/c1z2w3456789/article/details/10…...

基于SSM+Vue的学习交流论坛的设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用Vue技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…...

开发与运营:“开发”和“运营”角色有何不同和重叠?

开发和运营是促进软件系统交付的两种角色。大多数大规模构建软件的组织都会雇用这两个学科的人员。不过,开发和运维并不是完全孤立的。团队重叠并实现更高的吞吐量是很常见的。 在本文中,您将学习区分开发人员和操作人员之间的主要区别,以及它们重叠的方式。尽管有将两者结合…...

关于GD32引脚PA13、PA15、PB3、PB4配置为普通引脚的问题

关于GD32引脚PA13、PA15、PB3、PB4配置为普通引脚的问题 在实际开发中&#xff0c;经常会遇到引脚资源受限需要将一些具有特定功能的引脚配置为普通引脚或其他引脚功能使用的情况。 博主之前遇到过类似的情况&#xff0c;都正常解决了。但偶尔也会出现在配置引脚时少了一些配…...

JS-Dom转为图片,并放入pdf中进行下载

1、将dom转换为图片 这里我们使用html2canvas工具插件先将dom转为canvas元素然后canvas拥有一个方法可以将绘制出来的图形转为url然后下载即可注意&#xff1a;如果元素使用了渐变背景并透明的话&#xff0c;生成的图片可能会有点问题。我下面这个案例使用了渐变背景实现元素对…...

Python 无废话-办公自动化Excel格式美化

设置字体 在使用openpyxl 处理excel 设置格式&#xff0c;需要导入Font类&#xff0c;设置Font初始化参数&#xff0c;常见参数如下&#xff1a; 关键字参数 数据类型 描述 name 字符串 字体名称&#xff0c;如Calibri或Times New Roman size 整型 大小点数 bold …...

Python视频剪辑-Moviepy音频效果afx方法

随着多媒体内容在日常生活和工作中的广泛应用,音频处理成为了一个越来越重要的技能。无论是在游戏开发、音乐制作,还是在各种应用和网站中,高质量的音频处理都能极大地提升用户体验。然而音频处理看似复杂,实则不必如此。其实只需要掌握一些基础的概念和技巧,就能够完成大…...

让LLM模型输入token无限长

背景 增加LLM的输入token已经有很多的研究&#xff0c;但是思路无外乎&#xff1a;模型抽取局部特征通过上层通过模型融合预测最终解&#xff0c;以及这个思路的一些变种。然而这些思路其实都没能很彻底的解决无限长token问题&#xff0c;根据《EFFICIENT STREAMING LANGUAGE …...