JUC下的CompletableFuture详解

详细介绍

CompletableFuture是Java 8引入的一个实现Future接口的类,它代表一个异步计算的结果。与传统的Future相比,CompletableFuture提供了更丰富的功能,比如链式调用、组合异步操作、转换结果、异常处理等,极大地增强了Java在处理异步编程的能力。

核心API与方法
  1. 创建实例

    • CompletableFuture<Void/Type> supplyAsync(Supplier<U> supplier):在ForkJoinPool.commonPool()或自定义Executor中异步执行supplier,并返回一个新的CompletableFuture,其结果类型由Supplier决定。
    • CompletableFuture<Void> runAsync(Runnable runnable):同上,但Runnable没有返回值,因此CompletableFuture的类型为Void。
    • 还有非Async版本,如supplyAsync(Supplier<U> supplier, Executor executor),允许指定执行器。
  2. 链式调用与转换结果

    • .thenApply(Function<? super T,? extends U> fn):当前阶段正常完成时,使用给定的函数fn转换结果,并返回一个新的CompletableFuture。
    • .thenAccept(Consumer<? super T> action):当前阶段正常完成时,执行给定的action消费结果,无返回值。
    • .thenCompose(Function<? super T,? extends CompletionStage<U>> fn):当前阶段完成时,使用给定的函数fn将结果转换为另一个CompletionStage,并将其结果作为新的CompletableFuture。
  3. 组合异步操作

    • .thenCombine(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn):当两个CompletableFuture都完成时,使用BiFunction组合它们的结果。
    • .thenComposeBoth(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends CompletionStage<V>> fn):类似于.thenCombine(),但fn返回的是另一个CompletionStage,而非直接结果。
  4. 异常处理

    • .exceptionally(Function<Throwable,? extends T> fn):如果出现异常,使用提供的函数fn处理异常,并返回一个包含处理结果的新CompletableFuture。
    • .handle(BiFunction<? super T, Throwable, ? extends U> fn):无论正常完成还是异常结束,都会调用fn处理结果或异常,返回一个新的CompletableFuture。
  5. 完成与等待

    • .join():等待计算完成,如有异常直接抛出,适用于不需要区分成功失败的场景。
    • .get():同join,但此方法会抛出具体的ExecutionException或InterruptedException,便于区分错误类型。
  6. 触发动作

    • .whenComplete(BiConsumer<? super T,? super Throwable> action):无论CompletableFuture成功完成还是异常结束,都会执行action,但不改变结果或处理异常。
    • .whenCompleteAsync(BiConsumer<? super T,? super Throwable> action):异步版本的whenComplete。
实现原理浅析

   CompletableFuture基于Java的Fork/Join框架实现,利用工作窃取算法提高线程利用率。每个CompletableFuture对象内部维护了一个状态机,用于跟踪任务的完成状态(初始化、完成、取消、异常)。当一个阶段的任务完成时,它会触发后续阶段的任务开始执行,这一过程通过内部的等待队列和信号机制高效完成,实现了非阻塞的链式调用。

性能与最佳实践
  • 线程池选择:合理选择线程池,避免过度使用ForkJoinPool.commonPool(),尤其是在IO密集型任务中,应考虑使用自定义的固定大小线程池。
  • 避免过度链式:虽然链式调用使代码简洁,但过度使用可能导致逻辑难以理解和维护,适当使用中间变量命名可以帮助理解。
  • 异常传播与日志:确保异常处理逻辑完整,利用日志记录异常信息,便于调试。
  • 资源释放:在涉及资源操作的异步任务中,确保资源能够正确关闭或释放,避免资源泄露。

核心特性

1. 非阻塞式执行

CompletableFuture 允许任务异步执行,而不需要调用线程等待其完成。这意味着主线程可以继续执行其他操作,而不是被阻塞等待结果,提高了程序的响应性和并发性能。

2. 异步回调

它支持链式调用回调函数(如 thenApply, thenAccept, thenCompose 等),允许在异步操作完成时自动执行后续操作,无论是成功还是失败。这种机制使得复杂的异步流程控制变得简单直观。

3. 组合异步任务

CompletableFuture 提供了多种方法来组合多个异步操作,比如 thenCombine, thenCompose,可以将多个独立的异步任务按照依赖关系组织起来,实现任务间的流水线处理,提高程序的并发执行效率。

4. 异步转换结果

通过 thenApply 方法,可以在异步操作完成后,对结果进行转换或进一步处理,而无需等待整个过程完成,这对于构建复杂的异步工作流特别有用。

5. 错误处理

提供了错误处理机制,如 exceptionallyhandle 方法,可以优雅地捕获并处理异步执行过程中发生的异常,增强了程序的健壮性。

6. 自动线程管理

默认情况下,CompletableFuture 会使用 ForkJoinPool.commonPool() 来执行异步任务,但也可以通过 supplyAsync(Supplier<U> supplier, Executor executor) 方法指定自定义的 Executor,从而实现对线程资源的精细控制。

7. 可取消性

如同 FutureCompletableFuture 也支持任务的取消操作,通过 cancel(boolean mayInterruptIfRunning) 方法可以尝试取消一个正在执行或尚未开始的任务。

8. 延迟计算与懒初始化

通过 CompletableFuture.supplyAsync(Supplier<U> supplier) 方法,可以在需要结果时才真正触发计算,这对于资源敏感或计算密集型操作特别有用。

9. 高度可组合性

由于其丰富的API,CompletableFuture 可以轻松地与其他 CompletableFuture 实例组合,形成复杂的异步逻辑,而代码依然保持清晰和可维护。

10. 函数式编程风格

它鼓励使用函数式编程的思维,通过传递函数(Lambda表达式)来定义任务的执行逻辑和结果处理逻辑,使得代码更加简洁、易读且易于推理。

这些特性使 CompletableFuture 成为了Java中处理异步编程的强大工具,尤其是在构建高性能、响应式系统时,能够有效地提升代码的效率和可维护性。

使用场景

微服务架构中的异步通信

在微服务架构中,服务间通常通过HTTP、gRPC或其他协议进行通信。当一个服务需要调用多个下游服务获取数据时,如果采用同步方式,每个调用都会阻塞当前线程直到响应,这会大大增加请求的总耗时。使用CompletableFuture,可以并行发起这些调用,然后通过组合操作处理所有响应,显著减少整体响应时间,提升用户体验。

示例场景:电商平台的订单服务需要在创建订单时,异步调用库存服务检查商品库存、用户服务验证用户信息、支付服务预扣款等多个下游服务,使用CompletableFuture可以高效地并行处理这些任务。

数据库批量操作

对于大量数据的读写操作,如批量插入、更新或查询,直接在主线程中执行可能会导致长时间阻塞。通过CompletableFuture,可以将这些操作分批异步执行,每批操作完成后合并结果,既充分利用了系统资源,又保证了操作的高效性。

示例场景:系统日志收集模块,需要定时从多个来源拉取日志并存储到数据库中。利用CompletableFuture异步处理每个数据源的日志收集和存储任务,最后合并所有操作结果。

文件处理与上传下载

处理大文件的上传、下载或转换操作时,这些操作往往比较耗时,直接在主线程中执行会影响用户体验。使用CompletableFuture可以将这些操作放在后台执行,同时可以提供进度监控、结果通知等功能。

示例场景:云存储服务中,用户上传大文件后,服务端需要异步进行病毒扫描、格式转换等操作,完成后通知用户或进行下一步处理。

定时任务与周期性任务

在实现定时任务或周期性任务时,如定时数据分析、报表生成等,可以利用CompletableFuture配合ScheduledExecutorService进行异步调度,避免阻塞主线程,并且可以方便地控制任务的执行策略(如首次执行延迟、执行间隔等)。

示例场景:每天凌晨自动统计前一天的销售数据并生成报表,通过CompletableFuture安排在低峰时段异步执行,不影响白天的业务操作。

并发限制与资源池管理

在处理大量并发请求时,直接为每个请求分配线程可能会迅速耗尽资源。通过自定义线程池与CompletableFuture结合,可以有效控制并发数量,例如使用Semaphore限制并发数,确保系统稳定运行。

示例场景:在爬虫系统中,需要并发抓取大量网页,但不能无限制地创建线程。通过CompletableFuture结合Semaphore限制并发抓取的数量,既保证了抓取效率,又防止了资源耗尽。

使用示例:

通过一个具体的场景来展示其核心特性的应用:模拟在线购物平台中,用户下单后,系统需要异步执行三个任务——检查库存、验证用户支付能力和发送订单确认邮件,并在所有任务完成后,更新订单状态。

示例场景
  1. 检查库存:确认所购商品是否有足够的库存。
  2. 验证用户支付能力:检查用户的账户余额是否足够支付订单金额。
  3. 发送订单确认邮件:向用户邮箱发送订单确认信息。
代码示例:
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class CompletableFutureDemo {

    public static void main(String[] args) throws Exception {
        // 创建一个线程池,用于执行异步任务
        ExecutorService executor = Executors.newFixedThreadPool(3);

        // 模拟用户下单
        Order order = new Order(1, "Product A", 100);

        // 异步检查库存
        CompletableFuture<Boolean> stockCheckFuture = CompletableFuture.supplyAsync(
                () -> checkStock(order), executor);

        // 异步验证用户支付能力
        CompletableFuture<Boolean> paymentValidationFuture = CompletableFuture.supplyAsync(
                () -> validatePayment(order), executor);

        // 异步发送订单确认邮件
        CompletableFuture<Void> emailSendingFuture = CompletableFuture.runAsync(
                () -> sendConfirmationEmail(order), executor);

        // 当所有任务完成时,更新订单状态
        CompletableFuture<Void> allTasks = CompletableFuture.allOf(
                stockCheckFuture, paymentValidationFuture, emailSendingFuture);

        // 等待所有任务完成
        allTasks.get(); // 注意:在实际生产中应避免使用get()阻塞主线程,这里仅作示例

        // 假设所有任务成功完成,更新订单状态
        updateOrderStatus(order, true);

        executor.shutdown();
    }

    // 检查库存的模拟方法
    private static boolean checkStock(Order order) {
        // ... 实际库存检查逻辑
        System.out.println("Checking stock for order: " + order);
        return true; // 假设库存充足
    }

    // 验证用户支付能力的模拟方法
    private static boolean validatePayment(Order order) {
        // ... 实际支付能力验证逻辑
        System.out.println("Validating payment for order: " + order);
        return true; // 假设支付能力充足
    }

    // 发送订单确认邮件的模拟方法
    private static void sendConfirmationEmail(Order order) {
        // ... 实际邮件发送逻辑
        System.out.println("Sending confirmation email for order: " + order);
    }

    // 更新订单状态的模拟方法
    private static void updateOrderStatus(Order order, boolean isSuccess) {
        if (isSuccess) {
            System.out.println("Order " + order + " processed successfully. Status updated.");
        } else {
            System.out.println("Order processing failed for " + order);
        }
    }

    // 订单类
    static class Order {
        int orderId;
        String productName;
        int amount;

        public Order(int orderId, String productName, int amount) {
            this.orderId = orderId;
            this.productName = productName;
            this.amount = amount;
        }

        @Override
        public String toString() {
            return "Order{" +
                    "orderId=" + orderId +
                    ", productName='" + productName + '\'' +
                    ", amount=" + amount +
                    '}';
        }
    }
}
解释说明
  • 使用CompletableFuture.supplyAsync来启动异步任务,对于有返回值的任务(如库存检查和支付能力验证)。
  • 使用CompletableFuture.runAsync来启动无返回值的任务(如发送邮件)。
  • 利用CompletableFuture.allOf来组合多个CompletableFuture实例,等待所有任务完成。
  • 通过get()方法阻塞等待所有任务完成(实际应用中,应避免在主线程中阻塞等待,可以考虑使用.thenAccept().thenRun()等方法继续异步处理)。
  • 在所有异步任务完成后,调用updateOrderStatus方法更新订单状态。

注意事项

1. 线程池的选择与管理
  • 选择合适的线程池:默认情况下,如果没有显式提供ExecutorCompletableFuture会使用ForkJoinPool.commonPool(),这适合CPU密集型任务。对于IO密集型任务或大量短期任务,考虑自定义单线程或固定大小的线程池。
  • 避免资源耗尽:合理设置线程池大小,避免因创建过多线程而导致资源耗尽或系统崩溃。
  • 及时关闭线程池:使用完毕后,通过shutdownshutdownNow方法关闭线程池,尤其是对于短生命周期的线程池。
2. 异常处理
  • 全面的异常捕捉:确保每个可能抛出异常的异步操作都有相应的异常处理逻辑,如使用exceptionallyhandle方法。
  • 避免异常丢失:如果不恰当处理,异常可能在异步链中被忽略,导致问题难以追踪。
3. 避免过度链式调用
  • 代码可读性:过度的链式调用会使代码变得难以阅读和维护。适时使用局部变量存储中间结果,或拆分为多个方法。
  • 性能考量:链式调用过长可能导致不必要的复杂性,影响性能,尤其是在异常处理和资源管理方面。
4. 合理使用组合方法
  • 并行与串行:理解.thenCompose.thenCombine.thenApply等方法的区别,合理安排任务的执行顺序和依赖关系。
  • allOf与anyOf:使用.allOf等待所有任务完成,或.anyOf等待任一任务完成,根据需求选择合适的方法。
5. 避免阻塞操作
  • 非阻塞性质:尽量避免在使用CompletableFuture时调用阻塞方法,如.get(),除非确实需要阻塞等待结果。考虑使用.join()或异步处理结果。
  • 超时处理:如果必须使用.get(),考虑设置超时参数,防止无限等待。
6. 资源泄漏预防
  • 外部资源管理:在异步任务中使用外部资源(如数据库连接、文件句柄)时,确保资源被正确关闭,即使任务异常终止也要清理资源。
7. 测试与调试
  • 测试难度:异步代码的测试通常比同步代码更复杂。确保为异步流程编写充分的单元测试和集成测试。
  • 日志记录:适当添加日志记录,特别是关键路径和异常处理部分,有助于调试和问题定位。
8. 并发控制与限流
  • 并发控制:对于需要限制并发度的操作,如数据库写入,可以使用Semaphore等工具控制并发访问。
  • 限流保护:对于高负载场景,考虑在入口处实施限流策略,避免因并发度过高导致的服务不稳定。

优缺点

优点
  1. 提高并发性与响应性:通过非阻塞的异步执行,CompletableFuture允许程序在等待某个操作完成的同时执行其他任务,显著提高了系统的并发处理能力和响应速度。

  2. 链式编程与易于组合:丰富的API支持链式调用,使得异步任务的组织和逻辑流转清晰明了,易于构建复杂的异步流程。同时,提供了多种组合操作,如.thenCompose().thenCombine(),使得任务间的协作和依赖管理变得简单。

  3. 强大的异常处理机制:通过.exceptionally().handle()等方法,CompletableFuture允许开发者优雅地处理异步执行过程中的异常,确保程序的健壮性。

  4. 灵活性与可定制性:开发者可以根据需要选择不同的线程池执行异步任务,或者利用.supplyAsync().runAsync()的重载方法指定特定的Executor,提供了高度的灵活性和对资源的控制能力。

  5. 易于测试与调试:虽然异步编程通常比同步编程更难测试,但通过明确的链式调用和异常处理逻辑,CompletableFuture的代码结构相对清晰,有助于单元测试和调试。

缺点
  1. 代码可读性与维护性:虽然链式调用提高了编码效率,但在处理复杂逻辑时,过度的链式可能会导致代码变得冗长且难以阅读,特别是当涉及到多个条件分支和异常处理时。

  2. 潜在的资源管理问题:如果不小心管理,尤其是在大量使用CompletableFuture时,可能会导致线程池资源耗尽,或因忘记关闭资源(如数据库连接)而引发资源泄漏。

  3. 调试难度:异步编程的调试相较于同步编程更为复杂。异常的传播和处理路径可能跨越多个异步阶段,使得定位问题变得更加困难。

  4. 潜在的性能开销:虽然异步执行可以提升整体性能,但如果任务本身非常轻量级,创建和管理CompletableFuture实例以及线程上下文切换的开销可能会抵消异步带来的好处。

  5. 阻塞风险:尽管鼓励非阻塞使用,但在某些场景下,如直接使用.get()方法等待结果,可能会导致线程阻塞,影响程序的响应性。

可能遇到的问题及解决方案

1. 死锁问题

问题描述:在使用CompletableFuture时,如果存在相互等待的情况,可能导致死锁。例如,一个任务的完成依赖于另一个任务的结果,而后者又反过来等待前者的完成。

解决方案:仔细设计任务之间的依赖关系,避免循环等待。使用正确的组合方法,如.thenCompose()而不是.thenApply()来避免不必要的阻塞等待。

2. 资源泄漏

问题描述:未正确管理外部资源(如数据库连接、文件句柄)可能导致资源泄漏,尤其是在异步任务异常终止时。

解决方案:确保使用try-with-resources语句或finally块来确保资源被正确关闭。对于异步操作中的资源,考虑使用自定义的CompletableFuture,以便在任务完成或异常时清理资源。

3. 异常处理不当

问题描述:异常信息可能在异步链中丢失,尤其是当使用.thenApply().thenCompose()等方法时,异常不会自动传递到链的下一个阶段。

解决方案:积极使用.exceptionally().handle()方法来捕获和处理异常,确保异常信息被妥善处理并记录。

4. 性能瓶颈

问题描述:不合理的线程池配置或过度使用CompletableFuture可能导致线程创建过多,消耗过多系统资源,甚至引起OutOfMemoryError。

解决方案:根据任务特性合理配置线程池,例如,对于IO密集型任务,使用较大的线程池;对于CPU密集型任务,线程池大小应接近可用处理器的数量。避免过度创建CompletableFuture实例,必要时复用已完成的实例。

5. 代码可读性差

问题描述:过度的链式调用和复杂的异步逻辑可能会降低代码的可读性和可维护性。

解决方案:适度分解复杂逻辑,将长链式调用分解为多个小方法或使用中间变量存储结果。对于复杂的流程控制,考虑使用设计模式(如状态模式、责任链模式)来简化逻辑。

6. 调试困难

问题描述:异步执行的非线性特性使得通过日志或调试器跟踪程序流程变得困难。

解决方案:利用日志记录关键步骤和异常信息,使用条件断点和线程Dump分析工具来辅助调试。在设计阶段,尽量使异步逻辑清晰有序,便于追踪问题。

7. 过度阻塞

问题描述:虽然CompletableFuture鼓励非阻塞使用,但不当使用.get().join()方法可能导致主线程或工作线程阻塞。

解决方案:尽量使用.thenApply().thenAccept()等非阻塞方法处理结果。若需等待多个任务完成,优先使用.allOf().anyOf()结合.thenRun(),而非直接阻塞等待。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

给网络镜像模式下的 WSL2 使用 127.0.0.1代理的方法

网络镜像模式下的WSL2虽然复制了宿主机windows的ip&#xff0c;但是仍然无法访问127.0.0.1的代理。经过调查&#xff0c;发现因为WSL2从应用商店下载而来&#xff0c;所以可能是UWP应用&#xff0c;所以需要用工具解除环回代理限制。

mysql 不停的重启关闭

早上在使用phpstudy的时候&#xff0c;发现自己的mysql5.7和5.8都出现了问题&#xff0c;就是不停的重启&#xff0c;在梳理了状况之后&#xff0c;可能是硬盘的内存空间不足&#xff0c;或者硬盘出现了问题&#xff1b;于是我将mysql 重新安装了一次&#xff0c;整个问题就解决…

数据结构(四)————二叉树和堆(中)

制作不易&#xff0c;三连支持一下呗&#xff01;&#xff01;&#xff01; 文章目录 前言一、堆的概念及结构二、堆的实现三.堆的应用 总结 前言 CSDN 这篇博客介绍了二叉树中的基本概念和存储结构&#xff0c;接下来我们将运用这些结构来实现二叉树 一、堆的概念及结构 1…

一篇文章,系统性聊聊Java注解

你好&#xff01; 这类系统性聊聊***知识点的文章&#xff0c;是希望给大家带来对某个技术的全貌认识&#xff0c;如果大家喜欢&#xff0c;后续可以陆续更新此系列 下面&#xff0c;开始今天的分享 在之前&#xff0c;我们已经分享过注解相关的三个面试题&#xff0c; 今天的…

信号量、PV操作及软考高级试题解析

信号量 在并发系统中&#xff0c;信号量是用于控制公共资源访问权限的变量。信号量用于解决临界区问题&#xff0c;使得多任务环境下&#xff0c;进程能同步运行。此概念是由荷兰计算机科学家Dijkstra在1962年左右提出的。信号量仅仅跟踪还剩多少资源可用&#xff0c;不会跟踪…

Cloudera简介和安装部署

ChatGPT Cloudera 是一个基于 Apache Hadoop 的数据管理和分析平台。它是由 Hadoop 的几位创始人及早期贡献者于 2008 年创立的公司&#xff0c;并随着公司的不断发展&#xff0c;Cloudera 开始提供企业级的解决方案&#xff0c;帮助企业更好地利用 Hadoop 生态系统进行大数据…

2024.05.10作业

TCP服务器 头文件 #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QTcpServer> #include <QTcpSocket> #include <QList> #include <QMessageBox> #include <QDebug>QT_BEGIN_NAMESPACE namespace Ui { class Widget; …

mp4压缩怎么压缩?知道压缩原理和工具就会了!

在数字化时代&#xff0c;视频已成为我们生活中不可或缺的一部分。然而&#xff0c;随着视频质量的提升&#xff0c;文件大小也随之增加&#xff0c;给存储和传输带来了不小的挑战。因此&#xff0c;掌握MP4视频压缩技巧变得尤为重要。本文将为你详细介绍MP4压缩的多种方法&…

dev c++调试录入数字后回车直接关闭

1、我的dev c版本是5.11 2、输入7后&#xff0c;回车就没有了&#xff0c;原因是1013,1.cpp未包含在项目中 3、新建项目&#xff0c;并将test_debug.cpp包含在项目内&#xff0c;就可以下断点调试了

G.AB路线【蓝桥杯】/bfs+可重复走

AB路线 bfs可重复走 思路&#xff1a;本题和传统的bfs题目不同&#xff0c;本题为了满足题目先走K个A再走K个B&#xff0c;可能需要重复走某个格子才能继续走下去&#xff0c;故vis数组可以多开一维&#xff0c;vis[x][y][z]表示第z次走到x行y列这种情况是否出现过 A A A B B …

汇编语言——输入两个字数据(16位的数)X,Y,计算Z=X+Y,并把Z的结果显示出来

文章目录 以2进制输入&#xff0c;2进制输出&#xff08;无符号&#xff09;以2进制输入&#xff0c;2进制输出&#xff08;带符号&#xff09;以8进制输入&#xff0c;8进制输出以10进制输入&#xff0c;10进制输出以16进制输入&#xff0c;16进制输出 仅供参考 X、Y的输入可…

IATF16949认证是什么?

IATF16949认证是一项国际质量管理体系的认证标准&#xff0c;由国际汽车行业联合会&#xff08;IATF&#xff09;开发&#xff0c;旨在提高汽车行业的质量管理水平&#xff0c;满足客户对汽车部件和零部件的要求。该标准是在ISO/TS 16949标准的基础上&#xff0c;专门为汽车行业…

解决参考文献自动生成标号,换行时自动缩进

问题如下图所示&#xff0c;红色方框部分应该填充内容&#xff0c;但自动生成标号时不会填充&#xff1a; 解决方案&#xff1a; 1. 选中内容&#xff1a; 2. 找到布局-段落&#xff1a; 3. 选择“无”&#xff0c;即可。

【Linux操作系统】:文件操作

目录 前言 一、C语言中文件IO操作 1.文件的打开方式 2.fopen&#xff1a;打开文件 3.fread&#xff1a;读文件 4.fwrite:写文件 二、系统文件I/O 1.系统调用open、read、write 2.文件描述符fd 3.文件描述符的分配规则 4.重定向 5.缓冲区 6.理解文件系统 磁盘 磁盘…

富士Apeos 2350 NDA复印机报062 360代码故障

故障描述&#xff1a; 富士Apeos 2350 NDA复印机新机器刚拆箱安装&#xff0c;开机正常&#xff0c;自检扫描头一卡一卡的往前动几下就不动了、扫描灯也不亮扫描头也不能正常复位&#xff1b;按机器的复印键直接报062 360代码&#xff1b; 解答&#xff1a; 此代码为扫描故障&a…

多任务学习的优化算法:实现多个任务的最佳收敛

多任务学习的优化算法 多任务学习的优化算法&#xff1a;实现多个任务的最佳收敛多任务学习的挑战多任务学习的优化算法1. **梯度归一化&#xff08;Gradient Normalization, GradNorm&#xff09;**2. **多任务平衡&#xff08;Multi-Task Balancing, MTB&#xff09;**3. **弹…

Navicat工具连接人大金仓数据库

在使用人大金仓数据库时&#xff0c;可以选择使用人大金仓自带的连接工具&#xff0c;比如KingbaseES V8&#xff08;数据库开发管理工具&#xff09;工具&#xff0c;类似于navicat工具&#xff0c;两个工具都有优缺点&#xff0c;看个人喜好了。 但是在实际过程中&#xff0c…

pdffactory pro8.0虚拟打印机(附注册码)

PdfFactory pro是一款非常受欢迎的PDF虚拟打印机&#xff0c;可以帮助用户将你的其他文档保存为PDF格式。请为用户提供打印/发送/加密等多种实用功能&#xff0c;以及一套完善的PDF打印方案。 使用说明 下载pdfFactory Pro压缩包&#xff0c;解压后&#xff0c;双击exe文件&am…

【go项目01_学习记录10】

操作数据库 1 插入数据2 显示文章2.1 修改 articlesShowHandler() 函数2.2 代码解析 3 编辑文章3.1 添加路由3.2 编辑articlesEditHandler()3.3 新建 edit 模板3.4 代码重构3.5 完善articlesUpdateHandler()3.6 测试更新3.7 封装表单验证 1 插入数据 . . . func articlesStore…

Spark Streaming笔记总结(保姆级)

万字长文警告&#xff01;&#xff01;&#xff01; 目录 一、离线计算与流式计算 1.1 离线计算 1.1.1 离线计算的特点 1.1.2 离线计算的应用场景 1.1.3 离线计算代表技术 1.2 流式计算 1.2.1 流式计算的特点 1.2.2 流式计算的应用场景 1.2.3 流式计算的代表技术 二…