面试题: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进行代理,代理的核心是添加一个MethodInterceptor即AsyncExecutionInterceptor,该方法拦截器负责将方法真正的执行包装为任务,放入线程池中执行。

下面我们先使用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());}}

图片

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


总结

本文给出的只是一个方法,为了实现多线程事务一致性,我们还有很多方法,例如和本文一样的思想,直接利用JDBC提供的API来手动控制事务提交和回滚,或者可以尝试采用分布式事务的思路来解决问题。

大家之所以会被这个问题难住,主要是因为对Spring框架提供的便捷声明式事务支持中毒太深,以至于脑海中对事务的认知完全停留在@Transactional注解的层面,多了解底层基础设施,才能做到遇事不慌。

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

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

相关文章

JavaScript 第十八章(条件语句深度解析)

条件语句在编程中扮演着至关重要的角色&#xff0c;它们允许我们根据不同的条件执行不同的代码路径。在JavaScript中&#xff0c;我们主要通过if、else、else if和switch语句来实现这一功能。 if语句 if语句是最基础的条件判断语句&#xff0c;它允许我们在条件为真时执行代码…

QFile文件操作方法

一.概述 QFile 类支持对文件进行读取、写入、删除、重命名、拷贝等操作&#xff0c;它既可以操作文件文件&#xff0c;也可以操作二进制文件。 二.QFile方法 1.使用 QFile 读写文件之前必须先打开文件&#xff0c;调用 open() 成员方法即可&#xff0c;常用的语法格式为&…

强化学习 - Trust Region Policy Optimization (TRPO)

什么是机器学习 Trust Region Policy Optimization&#xff08;TRPO&#xff09;是一种策略梯度方法&#xff0c;用于解决强化学习问题。TRPO旨在通过限制策略更新的大小&#xff0c;提高训练的稳定性。这样可以防止在参数空间中迅速迭代导致过大的更新&#xff0c;从而保持策…

LandrayOA内存调优 / JAVA内存调优 / Tomcat web.xml 超时时间调优实战

目录 一、背景说明 二、LandrayOA / Tomcat 内存调优 2.1 \win64\tomcat\conf\web.xml 文件调优 2.2 \win64\tomcat\bin\catalina64.bat 文件调优 一、背景说明 随着系统的使用时间越来越长&#xff0c;数据量越多&#xff0c;发现系统的有些功能越来越慢&…

基于InceptionV2/InceptionV3/Xception不同参数量级模型开发构建中草药图像识别分析系统,实验量化对比不同模型性能

最近正好项目中在做一些识别相关的内容&#xff0c;我也陆陆续续写了一些实验性质的博文用于对自己使用过的模型进行真实数据的评测对比分析&#xff0c;感兴趣的话可以自行移步阅读即可&#xff1a; 《移动端轻量级模型开发谁更胜一筹&#xff0c;efficientnet、mobilenetv2、…

ubuntu 22 安装 node,npm,vue

1:安装 nodejs sudo apt update curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - sudo apt update && sudo apt install -y nodejs node -v 2:安装npm sudo npm install n -g npm -v 3:安装vite npm install vite -g 4:运行vue 把项目拷贝到…

大语言应用技术原理

大模型应用技术原理 RAG 向量数据库 对比 选型标准 开源vs.闭源vs. 源码可见客户端/SDK语言托管方式 self-hosted/on-premise redis,pgvector,milvusmanaged/cloud-native zilliz,pineconeembeded+cloud-native chroma,lanceDBself-hosted+cloud-native vald,drant,weaviate,vs…

OpenAI正式推出GPT商店 ChatGPT团队订阅服务一并推出

2024年1月11日消息&#xff0c;据外媒报道&#xff0c;如上周在给开发者的邮件中所宣布的一样&#xff0c;因ChatGPT而名声大噪的人工智能公司OpenAI&#xff0c;在本周正式推出了GPT商店&#xff0c;供用户分享和发现个性化的ChatGPT&#xff0c;同时他们也推出了面向各种不同…

git 基础操作

由于最近svn停止使用&#xff0c;github或gitlab等费用较高&#xff0c;所以最近尝试在本地用 git 现在本地进行代码管理。现总结一下 git 常用的操作命令和本地repository的创建步骤。 git init //初始化git status //查看文件夹状态git add . //添加文…

用C语言实现贪吃蛇游戏!!!

前言 大家好呀&#xff0c;我是Humble&#xff0c;不知不觉在CSND分享自己学过的C语言知识已经有三个多月了&#xff0c;从开始的C语言常见语法概念说到C语言的数据结构今天用C语言实现贪吃蛇已经有30余篇博客的内容&#xff0c;也希望这些内容可以帮助到各位正在阅读的小伙伴…

在 Redis 中使用 Lua 脚本执行复杂操作和事务

在 Redis 中使用 Lua 脚本执行复杂操作和事务 Redis 作为一个高性能的键值存储数据库&#xff0c;它的强大功能远不止于简单的数据存储和检索。Redis 自 2.6 版本起引入了对 Lua 脚本的支持&#xff0c;这意味着你可以在 Redis 服务器上直接运行 Lua 脚本。这一功能为执行复杂…

前端JavaScript篇之JavaScript 中如何进行隐式类型转换、加号(+)操作符什么时候用于字符串的拼接?为什么会有BigInt的提案?

目录 JavaScript 中如何进行隐式类型转换加法操作符&#xff08;&#xff09;比较操作符&#xff08;、>、<&#xff09;对象的隐式转换ToPrimitive 方法ToPrimitive 方法的作用ToPrimitive 方法的规则总结 加号&#xff08;&#xff09;操作符什么时候用于字符串的拼接&…

雨云VPS搭建PalWorld服务器,幻兽帕鲁开服联机教程(Windows),0基础保姆级教程

雨云VPS用Windows系统搭建幻兽帕鲁私服&#xff0c;PalWorld开服联机教程&#xff0c;零基础保姆级教程&#xff0c;本教程使用一键脚本来搭建幻兽帕鲁服务端&#xff0c;并讲了如何配置游戏参数&#xff0c;如何更新服务端等。 最近这游戏挺火&#xff0c;很多人想跟朋友联机…

实体关系抽取与属性补全的技术浅析

目录 前言1. 实体关系抽取2 实体关系抽取的方法2.1 基于模板的方法2.2 基于监督学习的关系抽取2.3 基于深度学习的关系抽取2.4 基于预训练语言模型的关系抽取 3 属性补全3.1 属性补全任务简介3.1 抽取式属性补全3.2 生成式属性补全 4 未来发展趋势结语 前言 在信息爆炸时代&am…

Python学习之路-Django基础:工程搭建

Python学习之路-Django基础:工程搭建 环境安装 创建虚拟环境 mkvirtualenv django_1.11.10 -p python3安装Django 使用Django 1.11.10版本 pip install django1.11.10创建工程 在使用Flask框架时&#xff0c;项目工程目录的组织与创建是需要我们自己手动创建完成的。在dj…

用甘特图有效管理多个项目进度

当公司或组织同时承担多个项目时,合理规划各项目的时间节点与资源分配对确保高效完成至关重要。采用甘特图可以直观地展示多个项目的时间进程、关键里程碑以及资源分配情况,便于从宏观层面全面把控各项目的动态。 在线甘特图软件 zz-plan.com 提供了非常强大的时间轴规划功能,支…

栈的基础知识

一、栈的定义与特点 栈&#xff1a;只能在表的一端&#xff08;栈顶&#xff09;进行插入和删除运算的线性表 逻辑结构 与线性表相同&#xff0c;仍为一对一关系 存储结构 用顺序栈和链栈存储均可&#xff0c;但顺序栈更常见 访问结点时依照后进先出&a…

漏洞复现-万户OA text2Html 任意文件读取(附漏洞检测脚本)

免责声明 文章中涉及的漏洞均已修复&#xff0c;敏感信息均已做打码处理&#xff0c;文章仅做经验分享用途&#xff0c;切勿当真&#xff0c;未授权的攻击属于非法行为&#xff01;文章中敏感信息均已做多层打马处理。传播、利用本文章所提供的信息而造成的任何直接或者间接的…

vue父子组件传值问题

在Vue中&#xff0c;父子组件之间的数据传递可以通过props和事件来实现。 使用props传递数据&#xff1a;父组件可以通过props将数据传递给子组件&#xff0c;子组件可以在模板中直接使用这些数据。父组件可以通过v-bind指令将数据绑定到子组件的props上。例如&#xff1a; v…

初识Cargo-Rust的包管理器

Cargo 是Rust的 包管理器。Cargo 会下载您Rust 的包依赖项&#xff0c;编译包&#xff0c;生成可分发的包&#xff0c;并将它们上传到crates.io - Rust 社区的包注册表。 安装的方法 安装 Rust 和 Cargo 获得 Cargo 的最简单方法是使用rustup脚本&#xff0c;获取当前稳定版本…