获取双异步返回值时,如何保证主线程不阻塞?

news2024/9/28 23:26:49

在这里插入图片描述

目录

    • 一、前情提要
    • 二、JDK8的CompletableFuture
      • 1、ForkJoinPool
      • 2、从ForkJoinPool和ThreadPoolExecutor探索CompletableFuture和Future的区别
    • 三、通过CompletableFuture优化 “通过Future获取异步返回值”
      • 1、通过Future获取异步返回值关键代码
        • (1)将异步方法的返回值改为```Future<Integer>```,将返回值放到```new AsyncResult<>();```中;
        • (2)通过```Future<Integer>.get()```获取返回值:
      • 2、通过CompletableFuture获取异步返回值关键代码
        • (1)将异步方法的返回值改为 int
        • (2)通过```completableFuture.get()```获取返回值
      • 3、效率对比
        • (1)测试环境
        • (2)统计四种情况下10万数据入库时间
        • (3)设置核心线程数
          • 自定义ForkJoinPool线程池
          • 自定义线程池
        • (4)统计分析
    • 四、通过CompletableFuture.allOf解决阻塞主线程问题
      • 1、语法
      • 2、代码实例
    • 五、CompletableFuture中花俏的语法糖
      • 1、runAsync
      • 2、supplyAsync
    • 六、顺序执行异步任务
      • 1、thenRun
      • 2、thenAccept
      • 3、thenApply
    • 七、CompletableFuture合并任务
    • 八、CompletableFuture VS Future总结
      • 在BUG中磨砺,在优化中成长

大家好,我是哪吒。

一、前情提要

在上一篇文章中,使用双异步后,如何保证数据一致性?,通过Future获取异步返回值,轮询判断Future状态,如果执行完毕或已取消,则通过get()获取返回值,get()是阻塞的方法,因此会阻塞当前线程,如果通过new Runnable()执行get()方法,那么还是需要返回AsyncResult,然后再通过主线程去get()获取异步线程返回结果。

写法很繁琐,还会阻塞主线程。

下面是FutureTask异步执行流程图:

在这里插入图片描述

二、JDK8的CompletableFuture

1、ForkJoinPool

Java8中引入了CompletableFuture,它实现了对Future的全面升级,可以通过回调的方式,获取异步线程返回值。

CompletableFuture的异步执行通过ForkJoinPool实现, 它使用守护线程去执行任务。

ForkJoinPool在于可以充分利用多核CPU的优势,把一个任务拆分成多个小任务,把多个小任务放到多个CPU上并行执行,当多个小任务执行完毕后,再将其执行结果合并起来。

Future的异步执行是通过ThreadPoolExecutor实现的。

在这里插入图片描述

2、从ForkJoinPool和ThreadPoolExecutor探索CompletableFuture和Future的区别

  1. ForkJoinPool中的每个线程都会有一个队列,而ThreadPoolExecutor只有一个队列,并根据queue类型不同,细分出各种线程池;
  2. ForkJoinPool在使用过程中,会创建大量的子任务,会进行大量的gc,但是ThreadPoolExecutor不需要,因为ThreadPoolExecutor是任务分配平均的;
  3. ThreadPoolExecutor中每个异步线程之间是相互独立的,当执行速度快的线程执行完毕后,它就会一直处于空闲的状态,等待其它线程执行完毕;
  4. ForkJoinPool中每个异步线程之间并不是绝对独立的,在ForkJoinPool线程池中会维护一个队列来存放需要执行的任务,当线程自身任务执行完毕后,它会从其它线程中获取未执行的任务并帮助它执行,直至所有线程执行完毕。

因此,在多线程任务分配不均时,ForkJoinPool的执行效率更高。但是,如果任务分配均匀,ThreadPoolExecutor的执行效率更高,因为ForkJoinPool会创建大量子任务,并对其进行大量的GC,比较耗时。

三、通过CompletableFuture优化 “通过Future获取异步返回值”

1、通过Future获取异步返回值关键代码

(1)将异步方法的返回值改为Future<Integer>,将返回值放到new AsyncResult<>();中;
@Async("async-executor")
public void readXls(String filePath, String filename) {
    try {
    	// 此代码为简化关键性代码
        List<Future<Integer>> futureList = new ArrayList<>();
        for (int time = 0; time < times; time++) {
            Future<Integer> sumFuture = readExcelDataAsyncFutureService.readXlsCacheAsync();
            futureList.add(sumFuture);
        }
    }catch (Exception e){
        logger.error("readXlsCacheAsync---插入数据异常:",e);
    }
}
@Async("async-executor")
public Future<Integer> readXlsCacheAsync() {
    try {
        // 此代码为简化关键性代码
        return new AsyncResult<>(sum);
    }catch (Exception e){
        return new AsyncResult<>(0);
    }
}
(2)通过Future<Integer>.get()获取返回值:
public static boolean getFutureResult(List<Future<Integer>> futureList, int excelRow) {
    int[] futureSumArr = new int[futureList.size()];
    for (int i = 0;i<futureList.size();i++) {
        try {
            Future<Integer> future = futureList.get(i);
            while (true) {
                if (future.isDone() && !future.isCancelled()) {
                    Integer futureSum = future.get();
                    logger.info("获取Future返回值成功"+"----Future:" + future
                            + ",Result:" + futureSum);
                    futureSumArr[i] += futureSum;
                    break;
                } else {
                    logger.info("Future正在执行---获取Future返回值中---等待3秒");
                    Thread.sleep(3000);
                }
            }
        } catch (Exception e) {
            logger.error("获取Future返回值异常: ", e);
        }
    }
    
    boolean insertFlag = getInsertSum(futureSumArr, excelRow);
    logger.info("获取所有异步线程Future的返回值成功,Excel插入结果="+insertFlag);
    return insertFlag;
}

2、通过CompletableFuture获取异步返回值关键代码

(1)将异步方法的返回值改为 int
@Async("async-executor")
public void readXls(String filePath, String filename) {
	List<CompletableFuture<Integer>> completableFutureList = new ArrayList<>();
   	for (int time = 0; time < times; time++) {
    	// 此代码为简化关键性代码
        CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(new Supplier<Integer>() {
	        @Override
	        public Integer get() {
	            return readExcelDbJdk8Service.readXlsCacheAsyncMybatis();
	        }
	    }).thenApply((result) -> {// 回调方法
	        return thenApplyTest2(result);// supplyAsync返回值 * 1
	    }).thenApply((result) -> {
	        return thenApplyTest5(result);// thenApply返回值 * 1
	    }).exceptionally((e) -> { // 如果执行异常:
	        logger.error("CompletableFuture.supplyAsync----异常:", e);
	        return null;
	    });
	
	    completableFutureList.add(completableFuture);
    }
}
@Async("async-executor")
public int readXlsCacheAsync() {
    try {
        // 此代码为简化关键性代码
        return sum;
    }catch (Exception e){
        return -1;
    }
}
(2)通过completableFuture.get()获取返回值
public static boolean getCompletableFutureResult(List<CompletableFuture<Integer>> list, int excelRow){
    logger.info("通过completableFuture.get()获取每个异步线程的插入结果----开始");

    int sum = 0;
    for (int i = 0; i < list.size(); i++) {
        Integer result = list.get(i).get();
        sum += result;
    }

    boolean insertFlag = excelRow == sum;
    logger.info("全部执行完毕,excelRow={},入库={}, 数据是否一致={}",excelRow,sum,insertFlag);
    return insertFlag;
}

3、效率对比

(1)测试环境
  1. 12个逻辑处理器的电脑;
  2. Excel中包含10万条数据;
  3. Future的自定义线程池,核心线程数为24;
  4. ForkJoinPool的核心线程数为24;
(2)统计四种情况下10万数据入库时间
  1. 不获取异步返回值
  2. 通过Future获取异步返回值
  3. 通过CompletableFuture获取异步返回值,默认ForkJoinPool线程池的核心线程数为本机逻辑处理器数量,测试电脑为12;
  4. 通过CompletableFuture获取异步返回值,修改ForkJoinPool线程池的核心线程数为24。

备注:因为CompletableFuture不阻塞主线程,主线程执行时间只有2秒,表格中统计的是异步线程全部执行完成的时间。

(3)设置核心线程数

将核心线程数CorePoolSize设置成CPU的处理器数量,是不是效率最高的?

// 获取CPU的处理器数量
int curSystemThreads = Runtime.getRuntime().availableProcessors() * 2;// 测试电脑是24

因为在接口被调用后,开启异步线程,执行入库任务,因为测试机最多同时开启24线程处理任务,故将10万条数据拆分成等量的24份,也就是10万/24 = 4166,那么我设置成4200,是不是效率最佳呢?

测试的过程中发现,好像真的是这样的。

自定义ForkJoinPool线程池
@Autowired
@Qualifier("asyncTaskExecutor")
private Executor asyncTaskExecutor;

@Override
public void readXls(String filePath, String filename) {
 	List<CompletableFuture<Integer>> completableFutureList = new ArrayList<>();
    for (int time = 0; time < times; time++) {
		CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(new Supplier<Integer>() {
	        @Override
	        public Integer get() {
	            try {
	                return readExcelDbJdk8Service.readXlsCacheAsync(sheet, row, start, finalEnd, insertBuilder);
	            } catch (Exception e) {
	                logger.error("CompletableFuture----readXlsCacheAsync---异常:", e);
	                return -1;
	            }
	        };
	    },asyncTaskExecutor);
	
	    completableFutureList.add(completableFuture);
	}

	// 不会阻塞主线程
    CompletableFuture.allOf(completableFutureList.toArray(new CompletableFuture[completableFutureList.size()])).whenComplete((r,e) -> {
        try {
            int insertSum = getCompletableFutureResult(completableFutureList, excelRow);
        } catch (Exception ex) {
            return;
        }
    });
}
自定义线程池
/**
 * 自定义异步线程池
 */
@Bean("asyncTaskExecutor")
public AsyncTaskExecutor asyncTaskExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    //设置线程名称
    executor.setThreadNamePrefix("asyncTask-Executor");
    //设置最大线程数
    executor.setMaxPoolSize(200);
    //设置核心线程数
    executor.setCorePoolSize(24);
    //设置线程空闲时间,默认60
    executor.setKeepAliveSeconds(200);
    //设置队列容量
    executor.setQueueCapacity(50);
    /**
     * 当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略
     * 通常有以下四种策略:
     * ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
     * ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
     * ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
     * ThreadPoolExecutor.CallerRunsPolicy:重试添加当前的任务,自动重复调用 execute() 方法,直到成功
     */
    executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
    executor.initialize();
    return executor;
}

在这里插入图片描述

(4)统计分析

效率对比:

③通过CompletableFuture获取异步返回值(12线程) < ②通过Future获取异步返回值 < ④通过CompletableFuture获取异步返回值(24线程) < ①不获取异步返回值

不获取异步返回值时性能最优,这不废话嘛~

核心线程数相同的情况下,CompletableFuture的入库效率要优于Future的入库效率,10万条数据大概要快4秒钟,这还是相当惊人的,优化的价值就在于此。

在这里插入图片描述

四、通过CompletableFuture.allOf解决阻塞主线程问题

1、语法

CompletableFuture.allOf(CompletableFuture的可变数组).whenComplete((r,e) -> {})

2、代码实例

getCompletableFutureResult方法在 “3.2.2 通过completableFuture.get()获取返回值”。

// 不会阻塞主线程
CompletableFuture.allOf(completableFutureList.toArray(new 		CompletableFuture[completableFutureList.size()])).whenComplete((r,e) -> {
    logger.info("全部执行完毕,解决主线程阻塞问题~");
    try {
        int insertSum = getCompletableFutureResult(completableFutureList, excelRow);
    } catch (Exception ex) {
        logger.error("全部执行完毕,解决主线程阻塞问题,异常:", ex);
        return;
    }
});

// 会阻塞主线程
//getCompletableFutureResult(completableFutureList, excelRow);

logger.info("CompletableFuture----会阻塞主线程吗?");

在这里插入图片描述

五、CompletableFuture中花俏的语法糖

1、runAsync

runAsync 方法不支持返回值。

可以通过runAsync执行没有返回值的异步方法。

不会阻塞主线程。

// 分批异步读取Excel内容并入库
int finalEnd = end;
CompletableFuture.runAsync(() -> readExcelDbJdk8Service.readXlsCacheAsyncMybatis();

2、supplyAsync

supplyAsync也可以异步处理任务,传入的对象实现了Supplier接口。将Supplier作为参数并返回CompletableFuture结果值,这意味着它不接受任何输入参数,而是将result作为输出返回。

会阻塞主线程。

supplyAsync()方法关键代码:

int finalEnd = end;
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(new Supplier<Integer>() {
    @Override
    public Integer get() {
        return readExcelDbJdk8Service.readXlsCacheAsyncMybatis();
    }
});
@Override
public int readXlsCacheAsyncMybatis() {
    // 不为人知的操作
   	// 返回异步方法执行结果即可
	return 100;
}

六、顺序执行异步任务

1、thenRun

thenRun()不接受参数,也没有返回值,与runAsync()配套使用,恰到好处。

// JDK8的CompletableFuture
CompletableFuture.runAsync(() -> readExcelDbJdk8Service.readXlsCacheAsyncMybatis())
.thenRun(() -> logger.info("CompletableFuture----.thenRun()方法测试"));

在这里插入图片描述

2、thenAccept

thenAccept()接受参数,没有返回值。

supplyAsync + thenAccept

  1. 异步线程顺序执行
  2. supplyAsync的异步返回值,可以作为thenAccept的参数使用
  3. 不会阻塞主线程
CompletableFuture.supplyAsync(new Supplier<Integer>() {
    @Override
    public Integer get() {
        return readExcelDbJdk8Service.readXlsCacheAsyncMybatis();
    }
}).thenAccept(x -> logger.info(".thenAccept()方法测试:" + x));

在这里插入图片描述

但是,此时无法通过completableFuture.get()获取supplyAsync的返回值了。

3、thenApply

thenApply在thenAccept的基础上,可以再次通过completableFuture.get()获取返回值。

supplyAsync + thenApply,典型的链式编程。

  1. 异步线程内方法顺序执行
  2. supplyAsync 的返回值,作为第 1 个thenApply的参数,进行业务处理
  3. 第 1 个thenApply的返回值,作为第 2 个thenApply的参数,进行业务处理
  4. 最后,通过future.get()方法获取最终的返回值
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(new Supplier<Integer>() {
	@Override
    public Integer get() {
        return readExcelDbJdk8Service.readXlsCacheAsyncMybatis();
    }
}).thenApply((result) -> {
    return thenApplyTest2(result);// supplyAsync返回值 * 2
}).thenApply((result) -> {
    return thenApplyTest5(result);// thenApply返回值 * 5
});

logger.info("readXlsCacheAsyncMybatis插入数据 * 2 * 5 = " + completableFuture.get());

在这里插入图片描述

七、CompletableFuture合并任务

  1. thenCombine,多个异步任务并行处理,有返回值,最后合并结果返回新的CompletableFuture对象;
  2. thenAcceptBoth,多个异步任务并行处理,无返回值;
  3. acceptEither,多个异步任务并行处理,无返回值;
  4. applyToEither,,多个异步任务并行处理,有返回值;

CompletableFuture合并任务的代码实例,这里就不多赘述了,一些语法糖而已,大家切记陷入低水平勤奋的怪圈。

八、CompletableFuture VS Future总结

本文中以下几个方面对比了CompletableFuture和Future的差异:

  1. ForkJoinPool和ThreadPoolExecutor的实现原理,探索了CompletableFuture和Future的差异;
  2. 通过代码实例的形式简单介绍了CompletableFuture中花俏的语法糖;
  3. 通过CompletableFuture优化了 “通过Future获取异步返回值”;
  4. 通过CompletableFuture.allOf解决阻塞主线程问题。

Future提供了异步执行的能力,但Future.get()会通过轮询的方式获取异步返回值,get()方法还会阻塞主线程。

轮询的方式非常消耗CPU资源,阻塞的方式显然与我们的异步初衷背道而驰。

JDK8提供的CompletableFuture实现了Future接口,添加了很多Future不具备的功能,比如链式编程、异常处理回调函数、获取异步结果不阻塞不轮询、合并异步任务等。

获取异步线程结果后,我们可以通过添加事务的方式,实现Excel入库操作的数据一致性。

异步多线程情况下如何实现事务?

有的小伙伴可能会说:

这还不简单?添加@Transactional注解,如果发生异常或入库数据量不符,直接回滚就可以了~

那么,真的是这样吗?我们下期见~


在BUG中磨砺,在优化中成长

使用双异步后,从 191s 优化到 2s

增加索引 + 异步 + 不落地后,从 12h 优化到 15 min

使用懒加载 + 零拷贝后,程序的秒开率提升至99.99%

性能优化2.0,新增缓存后,程序的秒开率不升反降


🏆文章收录于:100天精通Java从入门到就业

全网最细Java零基础手把手入门教程,系列课程包括:Java基础、Java8新特性、Java集合、高并发、性能优化等,适合零基础和进阶提升的同学。

🏆哪吒多年工作总结:Java学习路线总结,搬砖工逆袭Java架构师

华为OD机试 2023B卷题库疯狂收录中,刷题点这里

刷的越多,抽中的概率越大,每一题都有详细的答题思路、详细的代码注释、样例测试,发现新题目,随时更新,全天CSDN在线答疑。

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

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

相关文章

springboot-mybatis项目

一、后端开发环境搭建 1、File->New->Projet 2选择 Spring Initializr &#xff0c;然后选择默认的 url 点击next 3勾选Spring Web、SQL模板&#xff0c;next 4点击finish&#xff0c;搭建完成 二 数据库 1 新建数据库 2 执行sql建表 SET NAMES utf8mb4; SET FOREIGN…

嵌入式学习-C++-Day2

嵌入式学习-CDay2 一、思维导图 二、作业 1.封装一个矩形类(Rect)&#xff0c;拥有私有属性:宽度(width)、高度(height)&#xff0c;定义公有成员函数:初始化函数:void init(int w, int h) 更改宽度的函数:set_w(int w) 更改高度的函数:set_h(int h) 输出该矩形的周长和面积函…

ActiveMQ|01-ClassicArtemis功能介绍

接上篇-MQ消息队列主流消息服务规范及代表产品&#xff0c;ActiveMQ就是基于JMS消息服务规范的消息中间件组件&#xff0c;主要应用在分布式系统架构中&#xff0c;帮助构建高可用、 高性能、可伸缩的企业级面向消息服务的系统 本文速览&#xff1a; JMS对象模型ActiveMQ的功…

elementplus Dialog 对话框设置距离页面顶部的距离

默认为 15vh&#xff0c;当弹窗过于高的时候&#xff0c;这个距离其实是不合适的 <el-dialogv-model"dialogVisible"title"Tips"width"30%":before-close"handleClose"top"6vh"><span>This is a message</s…

LeetCode刷题---旋转链表

解题思路: 首先创建辅助节点cur遍历指向链表最后一个节点&#xff0c;并将cur的下一个节点指向head头节点&#xff0c;形成闭环&#xff0c;并且在遍历的过程中计算链表的长度n。 n-k表示当前节点&#xff08;当前节点指向未反转节点的最后一个节点&#xff09;到反转后的链表的…

输入框限制输入两位小数 输入金额限制 双向绑定输入框能继续输入但是变量的数据不变解决方案 input 保留两位小数

移动端项目 需求是 输入框只能输入1000以内的数字保留两位小数 开发中发现 用vue开发双向绑定 不管是用value还是v-model 在输入时用input监听输入框变化 校验是否匹配 当不匹配是修改绑定的变量 inputValue时 打印inputValue符合预期 但是input输入框中还是原来输入的值 没有…

对话框与多窗体设计 —— 自定义对话框及其调用

3.2 自定义对话框及其调用3.3 Splash与登录窗口 3.2 自定义对话框及其调用 在一个应用程序设计中&#xff0c;为了实现一些特定的功能&#xff0c;必 须设计自定义对话框。自定义对话框的设计一般从 QDialog继承&#xff0c;并且可以采用UI设计器可视化地设计对 话框。对话框的…

发送消息提醒学习使用

效果图 数据库表及实体模型参考 创建消息事件模型 public class SystemMsgSendEvent implements DomainEvent {private String eventId;//事件idprivate ExecutePoint executePoint;//执行方式 事务提交后当前线程private OperateInfo operateInfo;//操作信息private String …

【AIGC】Diffusers:AutoPipeline自动化扩散生图管道

前言 &#x1f917; 扩散器能够完成许多不同的任务&#xff0c;并且您通常可以将相同的预训练权重用于多个任务&#xff0c;例如文本到图像、图像到图像和修复。但是&#xff0c;如果您不熟悉库和扩散模型&#xff0c;可能很难知道将哪个管道用于任务。例如&#xff0c;如果您…

【博客搭建记录贴】day4_Hexo基本操作,添加草稿并发布

目录 1.将项目导入到开发环境1.1 先把项目导入到IDEA中1.2 确认IDEA中服务器启动正常 2.Hexo基本操作: 添加草稿并发布2.1 生成一个草稿文件2.2 在页面上查看草稿3.3 将草稿正式发布 1.将项目导入到开发环境 我本地已经安装了 IntelliJ IDEA&#xff08;版本&#xff1a;社区版…

web蓝桥杯真题--13、水果摆盘

背景介绍 目前 CSS3 中新增的 Flex 弹性布局已经成为前端页面布局的首选方式&#xff0c;这次试题将利用 Flex 实现经典布局效果。 准备步骤 在开始答题前&#xff0c;你需要在线上环境终端中键入以下命令&#xff0c;下载并解压所提供的文件。 wget https://labfile.oss.a…

某马头条——day11+day12

实时计算和定时计算 流式计算 kafkaStream 入门案例 导入依赖 <dependency><groupId>org.apache.kafka</groupId><artifactId>kafka-streams</artifactId><exclusions><exclusion><artifactId>connect-json</artifactId&…

仿真机器人-深度学习CV和激光雷达感知(项目2)day04【简单例程】

文章目录 前言简单例程运行小海龟仿真启动节点查看计算图发布 Topic调用 Serviece 用 Python 发布和接收 Topic创建工作空间创建功能包&#xff0c;编译编写 Topic Publisher 节点编写 Topic Subscriber 节点运行节点 自定义消息类型用 Python 注册和调用 Serviece新建功能包在…

更高效的大模型调优方法,华盛顿大学推出“代理调优”

随着ChatGPT等生成式AI产品朝着多模态发展&#xff0c;基础模型的参数越来越高&#xff0c;想进行权重调优需要耗费大量时间和AI算力。 为了提升模型的调优效率&#xff0c;华盛顿大学和艾伦AI实验室的研究人员推出了全新方法——Proxy Tuning&#xff08;代理调优&#xff09…

YOLOv7全网独家首发:Powerful-IoU更好、更快的收敛IoU,效果秒杀CIoU、GIoU等 | 2024年最新IoU

💡💡💡本文独家改进:Powerful-IoU更好、更快的收敛IoU,是一种结合了目标尺寸自适应惩罚因子和基于锚框质量的梯度调节函数的损失函数 💡💡💡MS COCO和PASCAL VOC数据集实现涨点 收录 YOLOv7原创自研 https://blog.csdn.net/m0_63774211/category_12511937.htm…

arcgis 线要素shp数据处理

回顾 上篇写了arcgis 点shp数据处理之少数点和批量点坐标如何生成点要素&#xff0c;这个可能在做一些设计及查询中需要做的第一步。那么今天将对如何点集转线、线要素编辑数据处理做一记录。 一、工具 arcToolbox工具箱、编辑器 二、操作方法 1.点集转线 还是用上篇处理成…

解决软件找不到msvcp120.dll的五种方法,快速修复msvcp120.dll文件丢失方法

msvcp120.dll 是一个动态链接库文件&#xff0c;它是 Microsoft Visual C 2013 的一个部分&#xff0c;具体来说是 Microsoft Visual C Redistributable Package 的一部分。这个文件包含了微软的 C 运行时库&#xff08;CRT&#xff09;&#xff0c;特别是 C 标准库的部分&…

一、Lamdba 表达式与函数式接口(最终版)

一、Lamdba 表达式与函数式接口 1.1 Lamdba 表达式与函数式接口 1.1.1 Lambda 表达式概述 Lambda 表达式是 Java 8 引入的一个新特性Lambda 表达式可以被视为匿名函数允许在需要函数的地方以更简洁的方法定义功能Lambda 表达式可以完成简洁的函数定义Stream API 中大量使用了…

目标检测数据集 - MS COCO

文章目录 1. 数据集介绍2. 使用pycocotools读取数据3. 验证mAP 论文&#xff1a;Microsoft COCO: Common Objects in Context 网址&#xff1a;https://arxiv.org/abs/1405.0312 官网&#xff1a;https://cocodataset.org/ 1. 数据集介绍 MS COCO是一个非常大型&#xff0c;且…

HAL STM32+EC11编码器实现增减调节及单击、双击、长按功能

HAL STM32EC11编码器实现增减调节及单击、双击、长按功能 &#x1f4fa;实现效果演示&#xff1a; &#x1f4d8;内容提要 &#x1f4dd;本文主要实现&#xff0c;通过STM32 HAL库开发&#xff0c;实现的EC11编码器功能&#xff0c;按键结合状态机思想实现的拓展单击、双击、…