《异步编程之美》— 全栈修仙《Java 8 CompletableFuture 对比 ES6 Promise 以及Spring @Async》

news2025/1/14 20:11:03

   哈喽,大家好!在平常开发过程中会遇到许多意想不到的坑,本篇文章就记录在开发过程中遇到一些常见的问题,看了许多博主的异步编程,我只能说一言难尽。本文详细的讲解了异步编程之美,是不可多得的好文,建议细细品尝。文章末尾附有思维导图以及参考文档。

        首先,我们得弄明白什么异步?JavaScript 语言的执行环境是单线程的,异步编程对于 JavaScript 来说必不可少。JavaScript 传统异步解决方案主要是通过回调函数,而回调函数最大的问题就是 Callback Hell。所以 ES6 标准提供的 Promise 对象,专门用于解决异步编程的问题。而 Java 语言是一个支持多线程的语言,语法以同步为主,在实际开发中很少需要用到大量的异步编程。但是要想追求更高的性能,异步通常是更好的选择。例如 Servlet 3 的异步支持、Spring 5 提供的 Spring WebFlux 等,都是为了追求更高的性能。和 JavaScript 一样,传统的 Callback 方式处理 Java 异步也会有 Callback Hell 问题,所以在 Java 8 中新增了和 ES6 的 Promise 类似的对象: java.util.concurrent.CompletableFuture 。

        其次,类似与前端 Promise 代表 异步对象,类似Java中的 CompletableFuture。Promise 是现代 JavaScript 中异步编程的基础,是一个由异步函数返回的可以向我们指示当前操作所处的状态的对象。在 Promise 返回给调用者的时候,操作往往还没有完成,但 Promise 对象可以让我们操作最终完成时对其进行处理(无论成功还是失败)。

        最后,@Async是Spring提供的一个异步注解,默认采用SimpleAsyncTaskExecutor线程池,该线程池不是真正意义上的线程池。使用此线程池无法实现线程重用,每次调用都会新建一条线程。若系统中不断的创建线程,最终会导致系统占用内存过高,引发OutOfMemoryError错误。所以我们得自定义线程池

public void execute(Runnable task, long startTimeout) {
  Assert.notNull(task, "Runnable must not be null");
  Runnable taskToUse = this.taskDecorator != null ? this.taskDecorator.decorate(task) : task;
  //判断是否开启限流,默认为否
  if (this.isThrottleActive() && startTimeout > 0L) {
    //执行前置操作,进行限流
    this.concurrencyThrottle.beforeAccess();
    this.doExecute(new SimpleAsyncTaskExecutor.ConcurrencyThrottlingRunnable(taskToUse));
  } else {
    //未限流的情况,执行线程任务
    this.doExecute(taskToUse);
  }

}

protected void doExecute(Runnable task) {
  //不断创建线程
  Thread thread = this.threadFactory != null ? this.threadFactory.newThread(task) : this.createThread(task);
  thread.start();
}

//创建线程
public Thread createThread(Runnable runnable) {
  //指定线程名,task-1,task-2...
  Thread thread = new Thread(this.getThreadGroup(), runnable, this.nextThreadName());
  thread.setPriority(this.getThreadPriority());
  thread.setDaemon(this.isDaemon());
  return thread;
}
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.ThreadPoolExecutor;

/**
 * 描述:异步配置2
 * 
 */
@Configuration
@EnableAsync    // 可放在启动类上或单独的配置类
public class AsyncConfiguration2 {
    @Bean(name = "asyncPoolTaskExecutor")
    public ThreadPoolTaskExecutor executor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        //核心线程数
        taskExecutor.setCorePoolSize(10);
        //线程池维护线程的最大数量,只有在缓冲队列满了之后才会申请超过核心线程数的线程
        taskExecutor.setMaxPoolSize(100);
        //缓存队列
        taskExecutor.setQueueCapacity(50);
        //许的空闲时间,当超过了核心线程出之外的线程在空闲时间到达之后会被销毁
        taskExecutor.setKeepAliveSeconds(200);
        //异步方法内部线程名称
        taskExecutor.setThreadNamePrefix("async-");
        /**
         * 当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略
         * 通常有以下四种策略:
         * ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
         * ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
         * ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
         * ThreadPoolExecutor.CallerRunsPolicy:重试添加当前的任务,自动重复调用 execute() 方法,直到成功
         */
        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        taskExecutor.initialize();
        return taskExecutor;
    }
}
//使用异步编程操作大量数据 

List<Long> collect = examSysQuestionReals.stream()
        .map(i -> CompletableFuture.supplyAsync(i::getId))
        .map(CompletableFuture::join)
        .collect(Collectors.toList());
        
 /** 与普通stream.map()对比
 优化后的代码中,stream.map() 分为两步:
第一次 map():将每个 examSysQuestionReal 对象转换为一个 CompletableFuture,该异步任务在后台线程(通常是线程池中的线程)中执行 i.getId() 方法。这意味着多个 getId() 操作可以同时进行,实现了并行处理。
第二次 map():对上一步产生的 CompletableFuture 列表调用 join() 方法,等待所有异步任务完成并获取它们的结果。这些结果随后被收集到一个新的 List<Long> 中。
区别总结:
执行模式:直接使用 stream.map() 时,映射操作是同步且顺序执行的;优化后的代码则利用 CompletableFuture 实现了异步并行执行。
性能:对于耗时的 getId() 操作(例如涉及数据库查询或其他远程服务调用),优化后的代码能利用多核处理器的能力,通过并行处理显著减少整体处理时间。而直接使用 stream.map() 只能在单一线程中顺序执行,无法利用多核优势,处理大量数据或耗时操作时可能效率较低。
资源消耗:优化后的代码引入了异步任务,可能增加线程池的使用和上下文切换的开销。然而,只要 getId() 操作的并行效益超过这些开销,总体上仍能提高性能。直接使用 stream.map() 无需额外的线程池资源,但在处理大规模或耗时任务时可能会因无法并行而显得低效。
综上所述,直接使用 stream.map() 适用于同步、轻量级且无需并行处理的场景。而优化后的代码结合 CompletableFuture 更适合处理可能耗时、受益于并行计算的任务,以提高程序的整体执行效率。在实际应用中,应根据具体需求和性能指标选择合适的方法
 /
 
//进阶,@Async与CompletableFuture
/**
在这个示例中,performComplexAsyncTask() 方法被标记为 @Async,由 Spring 异步执行。方法内部使用 CompletableFuture 实现了多个步骤的异步任务创建、组合和结果处理。这样既利用了 Spring 的异步方法抽象,又充分利用了 CompletableFuture 的灵活性和控制力。
总结来说,CompletableFuture 和 @Async 可以分别进行练习,然后在实践中结合使用,以适应不同复杂度和需求的异步编程场景。
/
@Service
public class AsyncService {

    @Async
    public CompletableFuture<String> performComplexAsyncTask(String input) {
        // Step 1: 异步获取数据
        CompletableFuture<String> dataFuture = CompletableFuture.supplyAsync(() -> {
            // 这里可能是耗时的数据库查询或网络请求
            return fetchData(input);
        });

        // Step 2: 异步处理数据,依赖于第一步的结果
        CompletableFuture<String> processedDataFuture = dataFuture.thenApply(this::processData);

        // Step 3: 异步发送通知,不依赖于前两步的结果,可以并发执行
        CompletableFuture<Void> notificationFuture = CompletableFuture.runAsync(() -> {
            sendNotification();
        });

        // Step 4: 等待所有异步操作完成
        return CompletableFuture.allOf(processedDataFuture, notificationFuture)
                .thenApply(unused -> {
                    // 返回最终处理结果或相关信息
                    return processedDataFuture.join();
                });
    }

    // 其他方法:fetchData(), processData(), sendNotification()
}

 /**
 在实际应用中,CompletableFuture 和 @Async 可以结合使用,发挥各自的优势。
使用 @Async 注解标记那些业务逻辑相对独立、适合作为单独任务执行的方法。这有助于简化代码结构,将异步处理逻辑从业务逻辑中分离出来。
在 @Async 方法内部,可以使用 CompletableFuture 构建更复杂的异步流程,如组合多个异步操作、处理中间结果等。这种方法结合了 Spring 的高级抽象与 CompletableFuture 的强大功能。
/  

现在我们从线程池配置类实战一次



import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * 配置线程池
 * 
 */
@Configuration
@EnableAsync
public class ExecutorConfig {


	/* 获取快递单号线程池,量大 配置 start */
    // 维持最小的线程数
    private int corePoolSizeAutoTrace = 10;
    // 最大的活动线程数
    private int maxPoolSizeAutoTrace = 50;
    // 排队的线程数
    private int queueCapacityAutoTrace = maxPoolSizeAutoTrace * 2;
    // 线程长时间闲置关闭的时间,单位秒
    private int keepAliveSecondsAutoTrace = 1 * 60 * 60;

    private String asyncTask = "asyncTask-";
    private String eventExecute = "eventExecute-";

    private String pushStatus = "pushStatus-";

    // 异常记录
    private int recordCoreSize = 5;
    // 最大的活动线程数
    private int recordMaxPoolSize = 10;


    // 异常记录
    private int orderCoreSize = 10;
    // 最大的活动线程数
    private int orderMaxPoolSize = 100;

    /**
     * 异步任务线程池
     * @return 执行器
     */
    @Bean
    public Executor asyncTask() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(corePoolSizeAutoTrace);
        executor.setMaxPoolSize(maxPoolSizeAutoTrace);
        executor.setQueueCapacity(queueCapacityAutoTrace);
        executor.setThreadNamePrefix(asyncTask);
        executor.setKeepAliveSeconds(keepAliveSecondsAutoTrace);
        executor.setThreadGroupName(asyncTask);
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }

    @Bean
    public Executor eventExecute() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(corePoolSizeAutoTrace);
        executor.setMaxPoolSize(maxPoolSizeAutoTrace);
        executor.setQueueCapacity(queueCapacityAutoTrace);
        executor.setThreadNamePrefix(eventExecute);
        executor.setKeepAliveSeconds(keepAliveSecondsAutoTrace);
        executor.setThreadGroupName(eventExecute);
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }

    @Bean
    public Executor pushStatus() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(corePoolSizeAutoTrace);
        executor.setMaxPoolSize(maxPoolSizeAutoTrace);
        executor.setQueueCapacity(queueCapacityAutoTrace);
        executor.setThreadNamePrefix(pushStatus);
        executor.setKeepAliveSeconds(keepAliveSecondsAutoTrace);
        executor.setThreadGroupName(pushStatus);
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }

    @Bean
    public Executor addOperationLogList() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(corePoolSizeAutoTrace);
        executor.setMaxPoolSize(maxPoolSizeAutoTrace);
        executor.setQueueCapacity(queueCapacityAutoTrace);
        executor.setThreadNamePrefix("addOperationLog-");
        executor.setKeepAliveSeconds(keepAliveSecondsAutoTrace);
        executor.setThreadGroupName("addOperationLog-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }

    /**
     * 新增托运单时,添加单据收发方信息
     * @return
     */
    @Bean
    public Executor addBillLogisticsInfo() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(corePoolSizeAutoTrace);
        executor.setMaxPoolSize(maxPoolSizeAutoTrace);
        executor.setQueueCapacity(queueCapacityAutoTrace);
        executor.setThreadNamePrefix("addBillLogisticsInfo-");
        executor.setKeepAliveSeconds(keepAliveSecondsAutoTrace);
        executor.setThreadGroupName("addBillLogisticsInfo-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }

    /**
     * 派车单异常记录
     */
    @Bean(name = "recordBoxExecutor")
    public Executor recordBoxExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(recordCoreSize);
        executor.setMaxPoolSize(recordMaxPoolSize);
        executor.setQueueCapacity(queueCapacityAutoTrace);
        executor.setThreadNamePrefix("addTmsBillRecordBoxDtl-");
        executor.setKeepAliveSeconds(keepAliveSecondsAutoTrace);
        executor.setThreadGroupName("addTmsBillRecordBoxDtl-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }

    /**
     * 运单回写揽收线程池
     */
    @Bean(name = "orderHandExecutor")
    public Executor orderHandExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(corePoolSizeAutoTrace);
        executor.setMaxPoolSize(maxPoolSizeAutoTrace);
        executor.setQueueCapacity(queueCapacityAutoTrace);
        executor.setThreadNamePrefix("orderHandExecutor-");
        executor.setKeepAliveSeconds(keepAliveSecondsAutoTrace);
        executor.setThreadGroupName("orderHandExecutor-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }

    /**
     * 运单回写签收线程池
     */
    @Bean(name = "orderSignExecutor")
    public Executor orderSignExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(corePoolSizeAutoTrace);
        executor.setMaxPoolSize(maxPoolSizeAutoTrace);
        executor.setQueueCapacity(queueCapacityAutoTrace);
        executor.setThreadNamePrefix("orderSignExecutor-");
        executor.setKeepAliveSeconds(keepAliveSecondsAutoTrace);
        executor.setThreadGroupName("orderSignExecutor-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
}

在代码中使用

PS:线程安全问题及解决方法

参考文档:

网址:CompletableFuture 指南 |贝尔东 (baeldung.com)

思维图解:CompletableFuture的使用| ProcessOn免费在线作图,在线流程图,在线思维导图

优雅处理并发:Java CompletableFuture最佳实践 - 个人文章 - SegmentFault 思否

Java 8 CompletableFuture 对比 ES6 Promise | 叉叉哥的BLOG (xxgblog.com)

SpringBoot 实现异步调用@Async | 以及使用@Async注解可能会导致的问题_springboot @async异步类被aop拦截会报什么错误-CSDN博客

 线上调优:接口响应慢?那是你没用 CompletableFuture 来优化!

一次真实生产事故,让我总结了线程池的正确使用方式 (qq.com)

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

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

相关文章

kalilinux - msf和永恒之蓝漏洞

Kali最强渗透工具 - metasploit metasploit是什么&#xff1f; msf是一款开源安全漏洞利用和测试工具&#xff0c;集成了各种平台上常见的溢出漏洞和流行的sheelcode&#xff0c;并持续保持更新。 具体操作 1、先切换到root用户&#xff0c;使用msfdb init命令初始化metaspl…

【大模型入门指南 11】大模型自动评估理论和实战

【大模型入门指南】系列文章&#xff1a; 【大模型入门指南 01】深度学习入门【大模型入门指南 02】LLM大模型基础知识【大模型入门指南 03】提示词工程【大模型入门指南 04】Transformer结构【大模型入门指南 05】LLM技术选型【大模型入门指南 06】LLM数据预处理【大模型入门…

【SOC 芯片设计 DFT 学习专栏 -- DFT 接管 clock 和 reset】

文章目录 OverviewDFT 接管 Clock 和 Reset 的方法Clock 接管方法Reset 接管方法 什么场景下需要 DFT 来接管 Clock 和 Reset&#xff1f;制造测试&#xff08;Manufacturing Test&#xff09;静态路径扫描测试&#xff08;Scan Testing&#xff09;调试与故障定位&#xff08;…

从 Conda 到 Pip-tools:Python 依赖管理全景探索20250113

从 Conda 到 Pip-tools&#xff1a;Python 依赖管理全景探索 引言 在 Python 开发中&#xff0c;依赖管理是一个"常见但复杂"的问题&#xff1a;一次简单的版本冲突可能让团队调试数小时&#xff1b;一次不受控的依赖升级可能让生产环境瘫痪。随着项目规模的增加和…

【数学】概率论与数理统计(五)

文章目录 [toc] 二维随机向量及其分布随机向量离散型随机向量的概率分布律性质示例问题解答 连续型随机向量的概率密度函数随机向量的分布函数性质连续型随机向量均匀分布 边缘分布边缘概率分布律边缘概率密度函数二维正态分布示例问题解答 边缘分布函数 二维随机向量及其分布 …

《自动驾驶与机器人中的SLAM技术》ch2:基础数学知识

目录 2.1 几何学 向量的内积和外积 旋转矩阵 旋转向量 四元数 李群和李代数 SO(3)上的 BCH 线性近似式 2.2 运动学 李群视角下的运动学 SO(3) t 上的运动学 线速度和加速度 扰动模型和雅可比矩阵 典型算例&#xff1a;对向量进行旋转 典型算例&#xff1a;旋转的复合 2.3 …

30_Redis哨兵模式

在Redis主从复制模式中,因为系统不具备自动恢复的功能,所以当主服务器(master)宕机后,需要手动把一台从服务器(slave)切换为主服务器。在这个过程中,不仅需要人为干预,而且还会造成一段时间内服务器处于不可用状态,同时数据安全性也得不到保障,因此主从模式的可用性…

苹果手机(IOS系统)出现安全延迟进行中如何关闭?

苹果手机&#xff08;IOS系统&#xff09;出现安全延迟进行中如何关闭&#xff1f; 一、设置二、隐私与安全性三、失窃设备保护关闭 一、设置 二、隐私与安全性 三、失窃设备保护关闭

【Oracle专栏】group by 和distinct 效率

Oracle相关文档&#xff0c;希望互相学习&#xff0c;共同进步 风123456789&#xff5e;-CSDN博客 1.背景 查阅资料&#xff1a; 1&#xff09;有索引情况下&#xff0c;group by和distinct都能使用索引&#xff0c;效率相同。 2&#xff09;无索引情况下&#xff0c;distinct…

linux:文件的创建/删除/复制/移动/查看/查找/权限/类型/压缩/打包,文本处理sed,awk

关于文件的关键词 创建 touch 删除 rm 复制 cp 权限 chmod 移动 mv 查看内容 cat(全部); head(前10行); tail(末尾10行); more,less,grep 查找 find 压缩 gzip ; bzip 打包 tar 编辑 sed 文本处理 awk 创建文件 格式&#xff1a; touch 文件名 删除文件 复制文…

day01-HTML-CSS——基础标签样式表格标签表单标签

目录 此篇为简写笔记下端1-3为之前笔记&#xff08;强迫症、保证文章连续性&#xff09;完整版笔记代码模仿新浪新闻首页完成审核不通过发不出去HTMLCSS1 HTML1.1 介绍1.1.1 WebStrom中基本配置 1.2 快速入门1.3 基础标签1.3.1 标题标签1.3.2 hr标签1.3.3 字体标签1.3.4 换行标…

哥大开发AI模型助力癌症和遗传病研究,近屿智能专注培养AI人才

近日&#xff0c;哥伦比亚大学瓦格洛斯医学院的研究团队在《自然》杂志上发表了一项重大研究成果。他们开发出一种名为“通用表达转换器”&#xff08;GET&#xff09;的新型AI模型&#xff0c;能够准确预测任何人类细胞中的基因活性&#xff0c;从而揭示细胞的内部工作机制。 …

9.4 visualStudio 2022 配置 cuda 和 torch (c++)

一、配置torch 1.Libtorch下载 该内容看了【Libtorch 一】libtorchwin10环境配置_vsixtorch-CSDN博客的博客&#xff0c;作为笔记用。我自己搭建后可以正常运行。 下载地址为windows系统下各种LibTorch下载地址_libtorch 百度云-CSDN博客 下载解压后的目录为&#xff1a; 2.vs…

基于spingbott+html+Thymeleaf的24小时智能服务器监控平台设计与实现

博主介绍&#xff1a;硕士研究生&#xff0c;专注于信息化技术领域开发与管理&#xff0c;会使用java、标准c/c等开发语言&#xff0c;以及毕业项目实战✌ 从事基于java BS架构、CS架构、c/c 编程工作近16年&#xff0c;拥有近12年的管理工作经验&#xff0c;拥有较丰富的技术架…

MySQL社区版下载及其环境配置(msi)

MySQL官网&#xff1a;https://www.mysql.com/ 1、进入官网 点击Downloads 找到MySQL Community (GPL) Downloads   然后点击MySQL Installer for Windows&#xff08;MySQL Community Server非向导式安装&#xff0c;而MySQL Installer for Windows有向导式安装&#x…

Linux-----进程通讯(消息队列)

目录 相关API 1.相关数据类型 mqd_t struct mq_attr struct timespec 2.相关系统调用接口 mq_open() mq_timedsend() && mq_send() mq_timedreceive() && mq_receive() mq_unlink() clock_gettime() 父子进程使用消息队列通讯 平行进程使用消息队列…

YOLOv8从菜鸟到精通(二):YOLOv8数据标注以及模型训练

数据标注 前期准备 先打开Anaconda Navigator&#xff0c;点击Environment&#xff0c;再点击new(new是我下载anaconda的文件夹名称)&#xff0c;然后点击创建 点击绿色按钮&#xff0c;并点击Open Terminal 输入labelimg便可打开它,labelimg是图像标注工具&#xff0c;在上篇…

mac homebrew配置使用

本文介绍mac上homebrew工具的安装、配置过程。homebrew功能类似于centos的yum&#xff0c;用于软件包的管理&#xff0c;使用上有命令的差异。 本次配置过程使用mac&#xff0c;看官方文档&#xff0c;在linux上也可以用&#xff0c;但我没试过&#xff0c;有兴趣的同学可以试试…

《使用 YOLOV8 和 KerasCV 进行高效目标检测》

《使用 YOLOV8 和 KerasCV 进行高效目标检测》 作者&#xff1a;Gitesh Chawda创建日期&#xff1a;2023/06/26最后修改时间&#xff1a;2023/06/26描述&#xff1a;使用 KerasCV 训练自定义 YOLOV8 对象检测模型。 &#xff08;i&#xff09; 此示例使用 Keras 2 在 Colab 中…

【Uniapp-Vue3】onShow和onHide钩子的对比和执行顺序

页面生命周期函数的执行顺序是onLoad>onShow>onReady&#xff0c;其中只有onReady中才能获取到DOM节点。 一、onShow函数 每一次的页面切入都会触发onShow函数。 import {onShow} from "dcloudio/uni-app"; onShow(()>{...}) 如果我点击“跳转页面1”再返…