深入理解Java并发:Future.get()与ExecutorService.awaitTermination()的超时机制

深入理解Java并发:Future.get()与ExecutorService.awaitTermination()的超时机制

本文深入探讨了Java并发编程中Future.get()与ExecutorService.awaitTermination()方法间的超时行为。通过分析一个常见误区,揭示了当两者结合使用时,实际等待时间并非简单取最短值,而是可能累加。文章详细解释了每个方法的阻塞特性及其对总执行时间的影响,并提供了专业的分析和建议,帮助开发者正确管理并发任务的生命周期和超时。

1. Future.get()与ExecutorService.awaitTermination()概述

在java并发编程中,executorservice是管理线程池的核心接口,而future则代表了异步计算的结果。

Future.get(long timeout, TimeUnit unit): 此方法用于阻塞性地获取异步任务的执行结果。如果在指定超时时间内任务未能完成,则会抛出TimeoutException。需要注意的是,get()方法是针对单个任务的,并且会阻塞当前调用线程直到任务完成或超时。ExecutorService.shutdown(): 此方法启动线程池的有序关闭,不再接受新任务,但会继续执行已提交的任务。ExecutorService.awaitTermination(long timeout, TimeUnit unit): 在调用shutdown()之后,此方法用于阻塞当前线程,直到所有已提交任务完成执行、或超时发生、或当前线程被中断。它等待的是整个线程池的终止,而非单个任务。

2. 误区分析:Future.get()与awaitTermination()的超时叠加

许多开发者可能会误以为,如果在Future.get()中设置了超时,同时又在ExecutorService.awaitTermination()中设置了另一个超时,那么总的等待时间将取两者中的最短值。然而,这是一种常见的误解。实际情况是,这两个超时机制是顺序发生且相互独立的。

考虑以下代码示例:

import java.util.ArrayList;import java.util.List;import java.util.concurrent.*;public class ExecutorTimeoutAnalysis {    // 假设 T 是任务返回的结果类型    static class MyTask implements Callable {        private final String name;        private final long sleepMillis;        public MyTask(String name, long sleepMillis) {            this.name = name;            this.sleepMillis = sleepMillis;        }        @Override        public String call() throws Exception {            System.out.println(Thread.currentThread().getName() + " - " + name + " started.");            try {                Thread.sleep(sleepMillis); // 模拟任务执行时间            } catch (InterruptedException e) {                Thread.currentThread().interrupt();                System.out.println(Thread.currentThread().getName() + " - " + name + " interrupted.");                throw e;            }            System.out.println(Thread.currentThread().getName() + " - " + name + " finished.");            return name + " Result";        }    }    public static void main(String[] args) {        ExecutorService executorService = Executors.newFixedThreadPool(2);        List<Callable> tasksList = new ArrayList();        tasksList.add(new MyTask("Task1", 1 * 60 * 1000)); // 任务1需要1分钟        tasksList.add(new MyTask("Task2", 1 * 60 * 1000)); // 任务2需要1分钟        List<Future> futures;        try {            // invokeAll 提交任务并返回 Future 列表,不阻塞当前线程            futures = executorService.invokeAll(tasksList);            // 1. 获取第一个任务的结果,设置5分钟超时            System.out.println("Attempting to get Task1 result with 5 minutes timeout...");            final String result1 = futures.get(0).get(5, TimeUnit.MINUTES);            System.out.println("Task1 Result: " + result1);            // 2. 获取第二个任务的结果,设置5分钟超时            // 此操作会在 Task1 的 get() 返回后才开始            System.out.println("Attempting to get Task2 result with 5 minutes timeout...");            final String result2 = futures.get(1).get(5, TimeUnit.MINUTES);            System.out.println("Task2 Result: " + result2);        } catch (InterruptedException | ExecutionException | TimeoutException e) {            System.err.println("Exception during task execution or retrieval: " + e.getMessage());        } finally {            // 3. 关闭 ExecutorService            executorService.shutdown();            System.out.println("ExecutorService shutdown initiated.");            // 4. 等待 ExecutorService 终止,设置30秒超时            // 此操作会在所有 Future.get() 调用完成后才开始            try {                System.out.println("Attempting to await termination with 30 seconds timeout...");                if (!executorService.awaitTermination(30, TimeUnit.SECONDS)) {                    System.out.println("ExecutorService did not terminate within 30 seconds. Forcing shutdown...");                    executorService.shutdownNow(); // 强制关闭                } else {                    System.out.println("ExecutorService terminated gracefully.");                }            } catch (InterruptedException e) {                Thread.currentThread().interrupt();                System.err.println("Await termination interrupted: " + e.getMessage());            }        }    }}

代码执行流程分析:

executorService.invokeAll(taskList): 提交两个任务到线程池。invokeAll本身是阻塞的,它会等待所有任务完成,或者被中断,或者抛出异常。但它返回的是一个List,这些Future对象在任务提交后立即可用,但其结果需要通过get()方法获取。

立即学习“Java免费学习笔记(深入)”;

更正: invokeAll的默认行为是阻塞直到所有任务完成或超时(如果指定了超时参数)。在上述代码中,invokeAll(taskList)没有指定超时,因此它会等待所有任务完成。这意味着在futures列表返回时,理论上所有任务已经完成。重要提示: 用户原问题中的代码片段没有给invokeAll设置超时,且在invokeAll之后立即调用了get()。如果invokeAll没有设置超时,它会阻塞直到所有任务完成。这意味着当futures列表返回时,任务可能已经完成。然而,为了更好地解释Future.get()的超时行为,我们假设invokeAll返回时任务可能仍在进行中(例如,如果invokeAll被替换为submit)。根据用户原问题上下文推断: 用户可能将invokeAll误解为非阻塞提交。如果invokeAll确实阻塞直到所有任务完成,那么后续的get()调用将立即返回结果,其5分钟的超时将不生效。但如果任务执行时间超过invokeAll的隐式超时(如果有的话),或者用户实际使用的是submit方法,那么get()的超时就会发挥作用。为了符合用户原意,我们假设任务在get()被调用时可能尚未完成。

重新分析基于用户原问题意图(即get()的超时是有效的):

假设invokeAll返回的Future列表,其对应的任务可能仍在后台执行,或者我们考虑的是submit方法。final task1 = tasksList.get(0).get(5, TimeUnit.MINUTES);: 调用线程会在这里阻塞,等待task1完成,最多等待5分钟。final task2 = tasksList.get(1).get(5, TimeUnit.MINUTES);: 只有在task1的get()方法返回后,此行代码才会执行。 调用线程会再次阻塞,等待task2完成,最多等待5分钟。executorService.shutdown();: 启动线程池关闭流程。此时,如果task1和task2都已通过get()获取了结果(即它们已经完成或超时),那么线程池中可能没有正在运行的用户任务。executorService.awaitTermination(30, TimeUnit.SECONDS);: 只有在task2的get()方法返回后,此行代码才会执行。 调用线程会在这里阻塞,等待线程池中的所有任务(如果有的话)完成,最多等待30秒。

总等待时间计算:

如果task1在5分钟内完成,但task2需要超过5分钟:task1.get() 最多等待5分钟。task2.get() 最多等待5分钟。awaitTermination() 最多等待30秒。总计最大等待时间 = 5分钟 (Task1) + 5分钟 (Task2) + 30秒 (awaitTermination) = 10分钟30秒。

这是因为Future.get()是串行调用的,每个get()都会独立地阻塞调用线程,直到其对应的任务完成或超时。awaitTermination()则是在所有get()调用之后才开始生效,它关注的是整个线程池的关闭,而不是单个任务的结果获取。因此,这些超时时间是累加的,而不是取最短值。

3. 管理并发任务超时的最佳实践

为了避免上述超时累加导致的总等待时间过长,或更精确地控制并发任务的整体超时行为,可以考虑以下策略:

使用invokeAll(Collection<? extends Callable> tasks, long timeout, TimeUnit unit):如果需要等待所有任务完成,并且对整个批处理操作有一个统一的超时限制,invokeAll的带超时参数版本是更合适的选择。它会阻塞直到所有任务完成,或者指定超时时间到达。如果超时,未完成的任务将被取消。

List<Future> futures = executorService.invokeAll(tasksList, 5, TimeUnit.MINUTES); // 整个批处理最多等待5分钟// 此时,futures 列表中的 Future 对象可能已经完成,也可能因超时而被取消for (Future future : futures) {    try {        if (future.isDone()) { // 检查任务是否完成            System.out.println("Task result: " + future.get()); // get()将立即返回结果或抛出异常        } else {            System.out.println("Task was not completed in time or cancelled.");        }    } catch (CancellationException | ExecutionException | InterruptedException e) {        System.err.println("Error getting task result: " + e.getMessage());    }}

使用CompletableFuture进行更灵活的超时控制:CompletableFuture提供了更强大的异步编程能力,包括超时处理。

单个任务超时:

CompletableFuture future1 = CompletableFuture.supplyAsync(() -> {    // Task1 logic    return "Task1 Result";}, executorService).orTimeout(5, TimeUnit.MINUTES); // 设置单个任务的超时

所有任务的组合超时:

CompletableFuture allOf = CompletableFuture.allOf(future1, future2, future3)                                .orTimeout(10, TimeUnit.MINUTES); // 所有任务在10分钟内完成try {    allOf.join(); // 阻塞等待所有任务完成或超时    System.out.println("All tasks completed within timeout.");} catch (CompletionException e) {    if (e.getCause() instanceof TimeoutException) {        System.err.println("One or more tasks timed out.");    } else {        System.err.println("An error occurred: " + e.getMessage());    }}

分离任务执行与结果获取逻辑:如果确实需要逐个获取任务结果,并且每个任务有独立的超时,那么串行调用get()是合理的。但要清楚这会导致总等待时间累加。如果目标是所有任务在一个整体时间段内完成,则应避免简单的串行get()。

谨慎使用shutdownNow():当awaitTermination()超时后,如果仍有未完成的任务,可以调用shutdownNow()来尝试中断它们。但这会中断正在执行的任务,可能导致数据不一致或其他副作用,应谨慎使用。

4. 总结

理解Future.get()和ExecutorService.awaitTermination()的超时行为至关重要。Future.get()的超时是针对单个任务的,并且串行调用会累加阻塞时间;而awaitTermination()的超时是针对整个线程池的关闭。在设计并发程序时,应根据实际需求选择合适的超时策略,例如使用invokeAll的带超时版本来控制批处理的整体超时,或利用CompletableFuture提供更细粒度和灵活的超时管理。清晰地规划任务的生命周期和错误处理机制,是构建健壮并发应用的关键。

以上就是深入理解Java并发:Future.get()与ExecutorService.awaitTermination()的超时机制的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/144111.html

赞 (0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
sql中的字符串常量是什么
上一篇 2025年12月1日 23:21:53
苹果爆料出现分歧!郭明錤:iPhone 16订单哪里增加了?
下一篇 2025年12月1日 23:21:54

相关推荐

  • 从太空看NVIDIA的 “奋进号” 总部:无处不在的三角形

    从太空看NVIDIA的 “奋进号” 总部:无处不在的三角形从太空看NVIDIA的 “奋进号” 总部:无处不在的三角形从太空看NVIDIA的 “奋进号” 总部:无处不在的三角形从太空看NVIDIA的 “奋进号” 总部:无处不在的三角形

    9月23日消息,在硅谷这片科技热地之中,每当一家科技公司脱颖而出,其总部大楼往往成业界关注的焦点。 NVIDIA,AI行业最为炙手可热的明星企业,市值已经高达4.3万亿美元,它的新总部大楼,一个为协作创新设计的未来办公场所,也承载了NVIDIA 的品牌理念。 NVIDIA总部大楼名为“Endeavo…

    2026年9月30日 • 用户投稿
    000
  • 将 XML Bean 定义迁移到 @Configuration 注解类

    将 XML Bean 定义迁移到 @Configuration 注解类 本文将指导您如何将 Spring 应用中基于 XML 的 JMS Bean 定义迁移到使用 @Configuration 注解的 Java 类中。通过示例,我们将展示如何使用 @Bean 注解创建 JmsTemplate 和 J…

    2026年9月30日
    000
  • windows资源监视器怎么用_资源监视器性能分析与使用指南

    资源监视器可精确定位电脑卡顿根源。通过Win+R输入resmon或任务管理器打开,利用CPU、内存、磁盘、网络标签页分别查看高占用进程、内存泄漏、I/O瓶颈及异常网络连接,结合排序与勾选功能深度分析系统性能问题。 如果您发现电脑运行缓慢、程序无响应或网络卡顿,可能是因为某些后台进程过度占用系统资源。…

    2026年9月30日
    000
  • Java中利用正则表达式实现字符串特定单词的忽略大小写替换

    本教程旨在解决如何在字符串中高效地将特定单词的所有大小写变体统一转换为指定大小写形式的问题。通过引入正则表达式的“大小写不敏感”匹配模式,我们能够避免为每种变体编写单独的替换规则,从而实现简洁、灵活且强大的字符串处理,显著提升代码效率和可维护性。 传统替换方法的局限性 在字符串处理中,我们经常会遇到…

    2026年9月30日
    000
  • 豆包AI安装后权限被系统阻止 豆包AI系统权限配置与安全例外设置

    豆包AI安装后权限被系统阻止 豆包AI系统权限配置与安全例外设置豆包AI安装后权限被系统阻止 豆包AI系统权限配置与安全例外设置豆包AI安装后权限被系统阻止 豆包AI系统权限配置与安全例外设置豆包AI安装后权限被系统阻止 豆包AI系统权限配置与安全例外设置

    本文将围绕豆包AI在安装后可能遇到的系统权限阻止问题进行详细说明。通过对系统安全设置的调整以及添加应用程序为例外项的操作,可以有效解决此问题。接下来的内容将分步骤引导您完成整个配置过程,旨在帮助您理解并顺利完成操作,确保豆包AI的正常运行。 ☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费…

    2026年9月30日 • 用户投稿
    000
  • 提升代码质量之Java枚举的正确用法_Java枚举类型的实战技巧

    提升代码质量之Java枚举的正确用法_Java枚举类型的实战技巧提升代码质量之Java枚举的正确用法_Java枚举类型的实战技巧提升代码质量之Java枚举的正确用法_Java枚举类型的实战技巧提升代码质量之Java枚举的正确用法_Java枚举类型的实战技巧

    Java枚举通过封装数据和行为,提供类型安全、避免魔法值,提升代码可读性与可维护性,如订单状态枚举可携带状态码、描述及处理逻辑,并支持策略模式,取代冗长if-else,实现清晰多态。 Java枚举,远不止是简单的常量集合,它是提升代码健壮性、可读性和可维护性的利器。通过它,我们能将一组固定的、有限的…

    2026年9月30日 • 用户投稿
    100
  • R包降级也不全是那么简单

    R包降级也不全是那么简单R包降级也不全是那么简单R包降级也不全是那么简单R包降级也不全是那么简单

    如果你是windows用户,可能会遇到make缺失的错误,如下图所示: 尽管我已经多年未使用Windows,但解决这个问题其实非常简单,只需通过谷歌搜索即可找到答案: 实际上,这是由于rtools缺失导致的问题。Windows用户只需下载适合的rtools版本并安装即可解决。 下载并安装Rtools…

    2026年9月30日 • 用户投稿
    000
  • sublime如何优化Ruby开发体验 sublime高效编写Ruby代码的技巧

    sublime如何优化Ruby开发体验 sublime高效编写Ruby代码的技巧sublime如何优化Ruby开发体验 sublime高效编写Ruby代码的技巧sublime如何优化Ruby开发体验 sublime高效编写Ruby代码的技巧sublime如何优化Ruby开发体验 sublime高效编写Ruby代码的技巧

    选择插件时应优先考虑代码提示、语法高亮、错误检查、代码片段、格式化和测试支持;2. 安装sublimerspec和testunit以便捷运行测试;3. 使用ruby bundle或自定义snippets提升编码速度;4. 配置sublimelinter与rubocop实现语法检查和代码格式化;5. …

    2026年9月30日 • 用户投稿
    300
  • 多模态AI模型部署硬件选型 多模态AI服务器配置推荐清单

    多模态AI模型部署硬件选型 多模态AI服务器配置推荐清单多模态AI模型部署硬件选型 多模态AI服务器配置推荐清单多模态AI模型部署硬件选型 多模态AI服务器配置推荐清单多模态AI模型部署硬件选型 多模态AI服务器配置推荐清单

    本文将围绕多模态AI模型部署的硬件选型问题展开叙述。首先,我们会深入分析多模态模型对硬件的核心需求,接着,将提供一个清晰的分步指南,讲解如何根据具体应用场景进行硬件的合理选配。最后,会给出一份具体的服务器配置推荐清单,帮助您直观地理解各组件的搭配方案,从而为您的AI项目构建一个高效、稳定的硬件基础。…

    2026年9月30日 • 用户投稿
    000
  • 华为Mate 80系列“十大黑科技”曝光 含新一代红枫镜头

    华为Mate 80系列“十大黑科技”曝光 含新一代红枫镜头华为Mate 80系列“十大黑科技”曝光 含新一代红枫镜头华为Mate 80系列“十大黑科技”曝光 含新一代红枫镜头华为Mate 80系列“十大黑科技”曝光 含新一代红枫镜头

    近日,数码博主曝光了华为mate 80系列旗舰手机的“十大黑科技”配置细节。作为华为2025年的重磅新品,该系列预计将于11月正式发布,涵盖四款机型:mate 80、mate 80 pro、mate 80 pro+以及mate 80 rs非凡大师版。 华为Mate 70系列 双层OLED屏幕技术突破…

    2026年9月30日 • 用户投稿
    100
  • Java中Try-Catch块与循环的正确使用方法

    Java中Try-Catch块与循环的正确使用方法Java中Try-Catch块与循环的正确使用方法Java中Try-Catch块与循环的正确使用方法Java中Try-Catch块与循环的正确使用方法

    本文旨在阐明在Java编程中,当try-catch块与for循环结合使用时,异常处理如何影响循环的执行流程。通过分析一个汽车租赁服务的示例,详细解释了将try-catch块放置在循环外部和内部的区别,并提供了正确的代码结构以确保程序在遇到异常时能够继续执行。 在Java编程中,合理地使用try-ca…

    2026年9月30日 • 用户投稿
    000
  • sublime如何实现AI代码补全 sublime集成Copilot的替代方案

    sublime如何实现AI代码补全 sublime集成Copilot的替代方案sublime如何实现AI代码补全 sublime集成Copilot的替代方案sublime如何实现AI代码补全 sublime集成Copilot的替代方案sublime如何实现AI代码补全 sublime集成Copilot的替代方案

    sublime text需通过插件实现ai代码补全,因其本身不集成ai功能,但可通过package control安装如tabnine等插件来实现;2. tabnine支持多语言和本地补全,保护隐私且无需订阅,但智能性略逊于copilot;3. 可通过编写自定义插件调用openai api实现补全,…

    2026年9月29日 • 用户投稿
    200
  • Try-Catch 块与 For 循环的协同工作:异常处理与循环控制

    Try-Catch 块与 For 循环的协同工作:异常处理与循环控制Try-Catch 块与 For 循环的协同工作:异常处理与循环控制Try-Catch 块与 For 循环的协同工作:异常处理与循环控制Try-Catch 块与 For 循环的协同工作:异常处理与循环控制

    在软件开发中,异常处理是确保程序稳定性的关键环节。当 try-catch 块与 for 循环结合使用时,异常的处理方式会直接影响循环的执行流程。理解这种交互对于编写健壮且能有效处理错误的程序至关重要。 Try-Catch 块与 For 循环的交互 正如文章摘要所说,本文深入探讨了在 try-catc…

    2026年9月29日 • 用户投稿
    100
  • 计算Java中两个日期时间之间的天、时、分、秒差

    计算Java中两个日期时间之间的天、时、分、秒差计算Java中两个日期时间之间的天、时、分、秒差计算Java中两个日期时间之间的天、时、分、秒差计算Java中两个日期时间之间的天、时、分、秒差

    本文旨在指导Java开发者如何计算给定日期时间(例如:”Wednesday 02-October-2022 11:51:1 PM”)与当前时间之间的天数、小时数、分钟数和秒数差。文章将详细介绍如何使用DateTimeFormatter解析日期和时间字符串,如何处理时区信息,以…

    2026年9月29日 • 用户投稿
    100
  • sublime如何实现代码自动补全 sublime代码自动补全功能的配置方法

    sublime如何实现代码自动补全 sublime代码自动补全功能的配置方法sublime如何实现代码自动补全 sublime代码自动补全功能的配置方法sublime如何实现代码自动补全 sublime代码自动补全功能的配置方法sublime如何实现代码自动补全 sublime代码自动补全功能的配置方法

    sublime text实现代码自动补全的核心是内置匹配、自定义片段和第三方插件的组合;2. 开箱即用的词汇匹配需结合package control安装插件如lsp、all autocomplete、emmet等以实现智能补全;3. 补全失效常见原因为语法模式错误、插件冲突、大型项目性能问题或语言服…

    2026年9月29日 • 用户投稿
    500
  • 豆包AI安装包哈希值验证 豆包AI文件完整性校验步骤

    豆包AI安装包哈希值验证 豆包AI文件完整性校验步骤豆包AI安装包哈希值验证 豆包AI文件完整性校验步骤豆包AI安装包哈希值验证 豆包AI文件完整性校验步骤豆包AI安装包哈希值验证 豆包AI文件完整性校验步骤

    本文将围绕豆包AI安装包的哈希值验证问题进行说明。哈希值,可以理解为文件的数字指纹,每个文件都有一个独特的、固定长度的哈希值。通过验证下载的安装包哈希值与官方提供的是否一致,可以有效判断文件在传输过程中是否被篡改或损坏,从而保障其完整性和安全性。接下来的内容将通过详细的步骤讲解,指导您如何获取官方哈…

    2026年9月29日 • 用户投稿
    100
  • 生成准确表达文章主题的标题在循环中使用 Try-Catch 块:确保程序持续运行

    生成准确表达文章主题的标题在循环中使用 Try-Catch 块:确保程序持续运行生成准确表达文章主题的标题在循环中使用 Try-Catch 块:确保程序持续运行生成准确表达文章主题的标题在循环中使用 Try-Catch 块:确保程序持续运行生成准确表达文章主题的标题在循环中使用 Try-Catch 块:确保程序持续运行

    本文探讨了在循环结构(特别是 for 循环)中使用 try-catch 块的正确方法。通过将 try-catch 块放置在循环内部,即使在某次迭代中发生异常,程序也能捕获并处理该异常,从而保证循环的其余部分继续执行,避免程序因异常而中断。本文将通过示例代码详细解释这种方法,并提供实际应用中的注意事项…

    2026年9月29日 • 用户投稿
    100
  • 生成准确表达文章主题的标题 使用 do-while 循环绘制矩形

    生成准确表达文章主题的标题
使用 do-while 循环绘制矩形生成准确表达文章主题的标题
使用 do-while 循环绘制矩形生成准确表达文章主题的标题
使用 do-while 循环绘制矩形生成准确表达文章主题的标题
使用 do-while 循环绘制矩形

    本文将介绍如何使用 Java 中的 do-while 循环来绘制一个指定高度和宽度的矩形。通过分析常见问题,我们将提供优化的代码示例,并解释关键步骤,帮助读者理解 do-while 循环的正确使用方法,避免无限循环等问题,从而掌握循环控制的核心概念。 在 java 编程中,do-while 循环是一…

    2026年9月29日 • 用户投稿
    100
  • PHP与SQL实现高效预约时间冲突检测教程

    本教程旨在详细指导如何在php应用程序中,利用sql查询高效检测预约时间冲突。通过构建包含精确时间重叠逻辑的`count(*)`查询,能够准确判断新提交的预约请求是否与数据库中现有预约发生冲突。这有助于避免重复预订,确保预约系统的准确性、可靠性及用户体验。 引言:预约系统中的时间冲突挑战 在开发任何…

    2026年9月29日
    100
  • C++编程的一些说明

    C++编程的一些说明C++编程的一些说明C++编程的一些说明C++编程的一些说明

    理解算法并无法将其转化为具体的代码实现,确实只能停留在理论阶段。要真正掌握编程,必须将理论知识应用到实践中。 所有编程语言在某种程度上是不分平台的,例如C++在Linux、MacOS和Windows上的基本语法和功能都是一致的。然而,具体的编译和运行环境可能会有所不同。 以下是视觉SLAM十四讲第二…

    2026年9月29日 • 用户投稿
    400

发表回复

登录后才能评论
关注微信