CompletableFuture 和 FutureTask

CompletableFuture 和普通的 Future 是 Java 并发编程中的两个接口,用于处理异步任务。它们在功能和实现上有显著的差异。CompletableFuture 是 Java 8 引入的一种改进型异步计算机制,而 Future 是 Java 5 中引入的较早版本。

1. 基本概念

  • Future:是一个用于表示异步任务的结果对象。它提供了检查任务是否完成、获取结果和取消任务的方法。它不能直接用于链式调用、合并多个异步任务,或者处理任务完成后的回调操作。
  • CompletableFuture:是 Future 的扩展,它实现了 FutureCompletionStage 接口。它不仅可以用于异步计算结果,还支持回调机制、组合任务和手动完成等更强大的功能。

2. 主要区别

2.1 非阻塞异步处理

  • 普通的 Future:调用 Future.get() 方法时,主线程会阻塞,直到异步任务完成。这种阻塞使得 Future 无法自然地进行异步回调或链式调用。javaCopy codeFuture<String> future = executorService.submit(() -> "Hello"); // 主线程阻塞,等待结果 String result = future.get();
  • CompletableFuture:支持非阻塞的异步处理,它可以通过 thenApply()thenAccept()thenRun() 等方法实现任务完成后的回调操作,并允许链式调用。javaCopy codeCompletableFuture.supplyAsync(() -> "Hello") .thenApply(result -> result + " World") .thenAccept(System.out::println); // 输出: Hello World

2.2 组合多个任务

  • 普通的 Future:缺乏组合功能,无法将多个异步任务组合在一起,需要额外的代码来处理多任务的合并。
  • CompletableFuture:支持多个异步任务的组合。可以使用 thenCombine()thenCompose()allOf() 等方法来合并任务。javaCopy codeCompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello"); CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World"); CompletableFuture<String> combined = future1.thenCombine(future2, (s1, s2) -> s1 + " " + s2); combined.thenAccept(System.out::println); // 输出: Hello World

2.3 异常处理

  • 普通的 Future:没有内置的异常处理机制,只能通过 try-catch 处理获取结果时抛出的异常。
  • CompletableFuture:提供了完善的异常处理机制,如 exceptionally()handle()whenComplete(),可以在链式调用中对异常进行处理。javaCopy codeCompletableFuture.supplyAsync(() -> { if (true) throw new RuntimeException("Error"); return "Hello"; }).exceptionally(ex -> "Recovered from error") .thenAccept(System.out::println); // 输出: Recovered from error

2.4 手动完成

  • 普通的 Future:只能在任务完成后获取结果,无法手动设置结果。
  • CompletableFuture:可以手动完成或手动抛出异常,可以使用 complete()completeExceptionally() 等方法。javaCopy codeCompletableFuture<String> future = new CompletableFuture<>(); future.complete("Completed manually"); future.thenAccept(System.out::println); // 输出: Completed manually

3. 底层实现原理

  • 普通的 Future:实现相对简单,基于阻塞模型。FutureTask 是其典型实现之一,它使用 volatile 标志位和 Condition 对象来确保线程的同步。
  • CompletableFuture
    • CompletionStage:它是基于回调的设计,用来支持异步编程。它实现了多种回调方法,允许在任务完成后执行各种回调。
    • ForkJoinPoolCompletableFuture 默认使用 ForkJoinPool.commonPool() 来执行异步任务,这样可以利用多线程的并行计算能力。
    • CAS(Compare-And-Swap)CompletableFuture 在内部实现中大量使用 CAS 操作来确保线程安全,避免了复杂的锁机制。
    • 任务队列CompletableFuture 在内部维护了一个任务队列,当一个任务完成时,会自动触发后续的回调任务,确保任务链的顺序执行。

4. 优缺点

特性FutureCompletableFuture
异步处理仅支持阻塞式处理支持非阻塞式链式处理
组合任务无组合功能支持任务组合和依赖任务
异常处理无内置的异常处理机制内置丰富的异常处理方法
手动完成无法手动设置结果可以手动完成或抛出异常
底层实现基于阻塞模型,线程同步简单基于回调和任务队列,依赖 ForkJoinPool 并发执行
适用场景简单的异步任务复杂的异步流、并行任务处理

5. 使用场景

  • 普通的 Future:适合简单的异步任务,尤其是当任务数量少且不需要复杂的依赖时,比如一次性异步请求的结果获取。
  • CompletableFuture:适合复杂的异步任务流、并行计算、任务依赖和需要细粒度异常处理的场景,如大型异步应用、事件驱动架构等。

总结

  • CompletableFuture 是对 Future 的全面改进,提供了非阻塞、组合、异常处理和手动完成等功能,非常适合现代 Java 并发编程的需求。
  • 如果需要复杂的异步任务处理或任务依赖链,CompletableFuture 是首选。相对而言,普通的 Future 适合简单的异步操作。

通过使用 CompletableFuture,Java 开发者可以更轻松地实现异步编程和并行计算,有助于提高程序的性能和响应速度。

4o

You said:

CompletableFuture 和 FutrueTask 的区别

ChatGPT said:

ChatGPT

CompletableFutureFutureTask 是 Java 中用于处理异步任务的两个重要类。它们的底层实现和用法有很大不同,适用于不同的异步编程场景。为了更好地理解它们的区别,我们需要深入底层代码和架构原理。

1. 基本概念

FutureTask

  • FutureTaskFuture 接口的实现类,属于 Java 5 中引入的早期异步任务处理机制。
  • 它是一个可以执行一次性任务的类,通常用于封装 Callable 或 Runnable 任务,提供任务执行结果的同步获取。
  • FutureTask 主要依赖于 锁和条件变量 来实现阻塞等待任务的完成。

CompletableFuture

  • CompletableFuture 是 Java 8 中引入的更高级的异步任务处理类,它是 FutureCompletionStage 的实现。
  • 它不仅可以执行异步任务,还支持任务组合回调链式调用手动完成、以及异常处理
  • 它主要依赖于 CAS(Compare-And-Swap)操作ForkJoinPool 提供的并行处理能力。

2. 底层实现的主要区别

2.1 FutureTask 的底层实现

FutureTask 是一个用于处理异步任务的同步工具。它通过锁和条件变量来控制任务的状态,并确保线程的安全性。

  • 任务状态FutureTask 维护一个状态变量来表示任务的不同状态,包括 NEWCOMPLETINGNORMALEXCEPTIONAL 等。该状态变量通过 volatile 修饰,以确保多线程环境下的可见性。
  • 同步机制FutureTask 的核心方法是 get(),它使用锁和条件变量(Condition)来实现阻塞等待。当任务完成时,会唤醒等待的线程。以下是核心逻辑:
    • get() 方法会先检查任务状态,如果未完成则进入等待状态。
    • 一旦任务执行完成,任务状态会被更新并释放锁,等待的线程被唤醒。
    • 内部依赖 synchronizedLockSupport.park() 进行线程的阻塞和唤醒。
  • 单次使用FutureTask 只能启动一次任务,不能进行链式调用或任务组合。
javaCopy codepublic V get() throws InterruptedException, ExecutionException {
    int s = state;
    if (s <= COMPLETING) {
        // Waits until the task is done
        awaitDone(false, 0L);
    }
    return report(s);
}

2.2 CompletableFuture 的底层实现

CompletableFuture 是一个更加现代化和功能丰富的异步处理工具,它的实现比 FutureTask 更加复杂和灵活。

  • 任务状态CompletableFuture 通过一组状态位来管理任务的完成状态,同时还维护一个回调队列,以实现任务链式调用。
  • 异步执行CompletableFuture 默认使用 ForkJoinPool 来执行异步任务。通过 supplyAsync()runAsync() 方法,可以将任务提交给公共线程池或指定线程池。
    • 它使用 CAS 操作来进行无锁状态更新,确保任务状态的原子性和线程安全。
  • 回调机制CompletableFuture 支持异步任务完成后的回调操作(如 thenApply()thenCompose()),这些操作通过任务队列和异步任务线程池来实现非阻塞的回调处理。
    • 回调任务被添加到任务队列中,当前一个任务完成时会自动触发后续任务的执行。
    • 异步回调的实现依赖 CompletionStage 接口的多种实现方法,如 handle()whenComplete()thenCombine() 等。
javaCopy codepublic <U> CompletableFuture<U> thenApply(Function<? super T, ? extends U> fn) {
    return uniApplyStage(null, fn);
}

// 内部使用 uniApplyStage() 方法来实现链式回调
private <U> CompletableFuture<U> uniApplyStage(
    Executor e, Function<? super T, ? extends U> fn) {
    if (fn == null) throw new NullPointerException();
    CompletableFuture<U> d = new CompletableFuture<>();
    if (e != null || !uniApply(this, fn, d, null)) {
        UniApply<T, U> c = new UniApply<>(e, this, fn, d);
        push(c);
        c.tryFire(SYNC);
    }
    return d;
}

3. 核心技术实现对比

3.1 同步机制

  • FutureTask:依赖显式的锁和条件变量,当任务未完成时,线程会被阻塞,直到任务完成并通知相关的等待线程。
  • CompletableFuture:基于回调机制和任务队列,通过 CAS 操作实现状态更新和线程安全。它避免了传统锁的使用,采用无锁编程的方式来实现高效的任务处理。

3.2 回调和组合

  • FutureTask:不支持任务的组合和回调,只能通过 get() 方法同步获取结果。
  • CompletableFuture:支持多种组合方式,如 thenApply()thenCompose()allOf()anyOf(),以及异常处理机制如 exceptionally()

3.3 并行和线程池的使用

  • FutureTask:任务执行由提交方(通常是 ExecutorService)管理,使用普通线程池或单个线程执行,不具备并行处理的能力。
  • CompletableFuture:通过 ForkJoinPool 或用户指定的线程池实现任务的并行执行,能够同时处理多个异步任务并合并结果。

4. 性能对比

  • 性能表现:在高并发环境下,CompletableFuture 由于无锁设计和并行处理,性能优于 FutureTask。它能够更好地利用多核 CPU 进行任务的并行处理,而 FutureTask 由于同步阻塞机制,在大量任务时可能会成为瓶颈。
  • 资源消耗CompletableFuture 在任务链较长时可能会消耗更多的内存,因为它需要维护回调队列。而 FutureTask 由于是一次性任务,相对内存占用较少。

5. 使用场景

  • FutureTask:适合简单的一次性任务执行,如异步计算和任务的同步获取。
  • CompletableFuture:适合复杂的异步流、任务组合、链式调用和异常处理。适用于大型并行计算、复杂的任务流控制和事件驱动的异步处理场景。

总结

  • CompletableFuture 提供了更现代化的异步任务处理方式,具有更丰富的功能和灵活性,更适合复杂的异步编程和并行计算。
  • FutureTask 更适合单次异步计算和简单的结果获取,适用范围较小,但实现更简单。

通过以上对比,我们可以清晰地看到 CompletableFutureFutureTask 在底层实现、任务管理和异步处理机制上的本质区别。

为了更好地理解 CompletableFutureFutureTask 的使用场景,下面分别展示具体的详细例子。

1. FutureTask 示例

场景:异步计算阶乘

使用 FutureTask 来异步计算一个数的阶乘,然后在主线程中获取结果。

javaCopy codeimport java.util.concurrent.*;

public class FutureTaskExample {
    public static void main(String[] args) {
        // 创建一个Callable任务,计算10的阶乘
        Callable<Long> factorialTask = () -> {
            long n = 10;
            long result = 1;
            for (long i = 1; i <= n; i++) {
                result *= i;
            }
            return result;
        };

        // 使用FutureTask包装Callable任务
        FutureTask<Long> futureTask = new FutureTask<>(factorialTask);

        // 使用线程执行任务
        Thread t = new Thread(futureTask);
        t.start();

        // 主线程继续执行其他操作
        System.out.println("主线程继续执行...");

        try {
            // 获取异步任务的结果
            Long result = futureTask.get();
            System.out.println("10的阶乘是: " + result);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
}

说明

  • FutureTask 任务定义:通过实现 Callable 接口来定义任务,它可以返回结果。
  • 线程启动任务:创建一个新线程来执行 FutureTask
  • 结果获取:通过调用 futureTask.get() 来获取任务结果,这会阻塞主线程,直到任务完成。

2. CompletableFuture 示例

场景:异步下载文件并解析数据

使用 CompletableFuture 来异步模拟文件下载和解析,并通过链式调用组合任务。

javaCopy codeimport java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

public class CompletableFutureExample {
    public static void main(String[] args) {
        // 模拟异步下载文件任务
        CompletableFuture<String> downloadTask = CompletableFuture.supplyAsync(() -> {
            try {
                System.out.println("开始下载文件...");
                TimeUnit.SECONDS.sleep(2); // 模拟下载延迟
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "文件内容";
        });

        // 文件下载完成后,解析文件内容
        CompletableFuture<String> parseTask = downloadTask.thenApply(content -> {
            System.out.println("解析文件内容...");
            return "解析后的数据:" + content;
        });

        // 处理最终结果
        parseTask.thenAccept(result -> System.out.println("处理结果: " + result));

        // 主线程继续执行其他操作
        System.out.println("主线程继续执行...");
        
        // 等待所有异步任务完成
        parseTask.join();
    }
}

说明

  • 链式调用CompletableFuture 允许通过链式调用将多个任务组合在一起(如 thenApply())。
  • 非阻塞处理:主线程不会因为异步任务而阻塞,可以继续执行其他任务。
  • 异步执行:使用 supplyAsync() 异步启动下载任务,并通过 thenApply()thenAccept() 进行后续处理。

3. 复杂组合示例

场景:使用 CompletableFuture 进行复杂的异步任务组合

此示例展示了如何并行处理两个任务,并在完成后合并结果。

javaCopy codeimport java.util.concurrent.CompletableFuture;

public class CompletableFutureCombineExample {
    public static void main(String[] args) {
        // 异步任务1:模拟获取用户信息
        CompletableFuture<String> userTask = CompletableFuture.supplyAsync(() -> {
            System.out.println("获取用户信息...");
            return "用户信息";
        });

        // 异步任务2:模拟获取订单信息
        CompletableFuture<String> orderTask = CompletableFuture.supplyAsync(() -> {
            System.out.println("获取订单信息...");
            return "订单信息";
        });

        // 组合两个异步任务的结果
        CompletableFuture<String> combinedTask = userTask.thenCombine(orderTask, (user, order) -> {
            return user + " 和 " + order;
        });

        // 处理最终结果
        combinedTask.thenAccept(result -> System.out.println("结果: " + result));

        // 等待所有异步任务完成
        combinedTask.join();
    }
}

说明

  • thenCombine() 用于任务组合:它允许将两个异步任务的结果合并。
  • 任务并行执行:两个任务(获取用户和订单信息)是并行执行的,并在合并时同步。

4. 异步异常处理

场景:CompletableFuture 的异常处理

在异步任务中,CompletableFuture 提供了内置的异常处理机制。

javaCopy codeimport java.util.concurrent.CompletableFuture;

public class CompletableFutureExceptionExample {
    public static void main(String[] args) {
        // 异步任务:模拟可能抛出异常的任务
        CompletableFuture<String> errorTask = CompletableFuture.supplyAsync(() -> {
            if (true) {
                throw new RuntimeException("任务失败!");
            }
            return "任务成功";
        });

        // 使用 exceptionally() 处理异常
        CompletableFuture<String> handledTask = errorTask.exceptionally(ex -> {
            System.err.println("捕获异常: " + ex.getMessage());
            return "默认结果";
        });

        // 处理最终结果
        handledTask.thenAccept(result -> System.out.println("最终结果: " + result));

        // 等待任务完成
        handledTask.join();
    }
}

说明

  • exceptionally():用来处理异步任务中的异常,类似于 catch 块。
  • 异常回退:在出现异常时返回一个默认结果,确保任务链的继续执行。

总结

特性FutureTaskCompletableFuture
基本功能异步执行任务和阻塞获取结果异步执行任务、链式调用、组合任务、异常处理
非阻塞性无法实现非阻塞处理,get() 阻塞支持非阻塞,允许链式调用和异步回调
组合任务无法实现任务组合可以通过 thenCombine()allOf() 等方法组合任务
异常处理只能在 get() 时捕获异常提供 exceptionally()handle() 等异常处理方法
  • FutureTask 适合单一、简单的异步任务处理场景。
  • CompletableFuture 更适合复杂的异步任务流处理,提供更多的功能和灵活性。

希望这些例子能帮助你深入理解两者的差异及其使用场景!

在电商场景中,FutureTaskCompletableFuture 都可以应用于处理异步任务,但它们在不同的使用场景下有各自的优势。以下是两者在电商环境中的详细应用示例:

1. FutureTask 在电商场景中的应用

1.1 异步加载商品详情

在电商网站中,用户点击商品后需要加载详细信息,包括图片、价格、描述、评论等。FutureTask 可用于一次性异步加载这些数据。

  • 场景描述:当用户点击商品时,系统会启动一个 FutureTask 来异步获取商品的所有信息。
  • 实现逻辑
    • 创建多个 FutureTask,分别用于加载商品图片、描述和用户评价。
    • 使用线程池执行任务。
    • 在前端页面加载完成后,通过 future.get() 获取结果并进行展示。

1.2 短期的支付状态轮询

在支付流程中,可能需要短时间内轮询支付状态,以确保用户的支付结果在页面上快速更新。

  • 场景描述:当用户完成支付后,启动一个 FutureTask,每隔几秒检查一次支付状态,直到成功或超时。
  • 实现逻辑
    • 使用 FutureTask 包装轮询支付状态的任务,并设置超时时间。
    • 使用 get(long timeout, TimeUnit unit) 获取结果,确保在一定时间内轮询。

1.3 用户认证或授权

在用户登录、权限校验等需要短时间响应的任务中,可以使用 FutureTask 来进行一次性验证。

  • 场景描述:在用户登录时,验证用户名和密码,并返回验证结果。
  • 实现逻辑
    • 使用 FutureTask 异步执行认证逻辑,并在结果完成后,返回登录状态或跳转至用户首页。

2. CompletableFuture 在电商场景中的应用

2.1 并行加载商品详情

电商网站在加载商品页面时,需要同时加载多个信息(如库存状态、相关商品推荐、促销信息等)。CompletableFuture 可以通过并行任务组合来高效处理。

  • 场景描述:当用户点击商品页面时,同时启动多个 CompletableFuture 来加载相关信息,如库存状态、推荐商品、促销活动等。
  • 实现逻辑
    • 使用 supplyAsync() 并行加载不同的信息。
    • 通过 thenCombine() 将多个异步任务的结果合并。
    • 使用 exceptionally() 捕获并处理加载过程中可能出现的异常,保证页面不会因某一模块的加载失败而导致整体崩溃。
javaCopy codeCompletableFuture<String> stockFuture = CompletableFuture.supplyAsync(() -> loadStockInfo(productId));
CompletableFuture<String> promoFuture = CompletableFuture.supplyAsync(() -> loadPromoInfo(productId));
CompletableFuture<String> combinedFuture = stockFuture.thenCombine(promoFuture, (stock, promo) -> stock + " " + promo);

2.2 异步订单处理

在订单处理流程中,CompletableFuture 可以用于将多个步骤(如验证库存、计算折扣、生成订单)进行异步处理。

  • 场景描述:用户提交订单后,系统需要同时验证库存、计算价格、应用优惠券,并生成订单。
  • 实现逻辑
    • 使用 thenCompose()thenApply() 进行链式调用,先验证库存,再计算价格和折扣,最后生成订单。
    • 异步任务之间可以通过 handle() 捕获异常,确保整个订单流程的可控性。
javaCopy codeCompletableFuture<Void> orderFuture = CompletableFuture.supplyAsync(() -> checkStock(productId))
    .thenApply(stock -> calculateDiscount(stock))
    .thenApply(discount -> createOrder(userId, productId, discount))
    .thenAccept(orderId -> sendConfirmation(orderId));

2.3 异步推荐系统

在个性化推荐系统中,可以利用 CompletableFuture 来并行处理用户历史购买记录、浏览行为和其他推荐源的结果,并将结果合并为最终推荐列表。

  • 场景描述:用户登录后,系统需根据用户的历史订单、浏览行为、相似用户的购买记录等来生成推荐商品列表。
  • 实现逻辑
    • 分别启动多个 CompletableFuture,获取用户的历史订单、浏览行为等信息。
    • 使用 allOf() 将所有异步任务的结果合并为一个推荐列表。
    • 异常处理通过 whenComplete() 来确保任务的完成状态和错误处理。
javaCopy codeCompletableFuture<List<Product>> historyFuture = CompletableFuture.supplyAsync(() -> getUserHistory(userId));
CompletableFuture<List<Product>> browseFuture = CompletableFuture.supplyAsync(() -> getUserBrowse(userId));
CompletableFuture<List<Product>> similarFuture = CompletableFuture.supplyAsync(() -> getSimilarUsers(userId));

CompletableFuture<List<Product>> recommendation = CompletableFuture.allOf(historyFuture, browseFuture, similarFuture)
    .thenApply(v -> mergeResults(historyFuture.join(), browseFuture.join(), similarFuture.join()));

2.4 支付状态异步轮询和超时

支付完成后,系统需要进行支付状态的轮询和超时处理。CompletableFuture 可以实现异步的非阻塞式轮询。

  • 场景描述:用户完成支付后,系统需要异步轮询支付网关的状态,直到支付成功或超时。
  • 实现逻辑
    • 使用 supplyAsync() 异步调用支付状态接口。
    • thenApply() 中处理支付结果。
    • 使用 orTimeout() 设置超时。
javaCopy codeCompletableFuture<String> paymentFuture = CompletableFuture.supplyAsync(() -> checkPaymentStatus(paymentId))
    .thenApply(status -> processPaymentStatus(status))
    .orTimeout(30, TimeUnit.SECONDS)
    .exceptionally(ex -> "支付超时");

3. 对比总结

应用场景FutureTaskCompletableFuture
单次异步任务简单的异步任务,如订单状态轮询和登录验证同样适用,但一般用于多任务组合的场景
并行任务处理不支持并行组合支持多个异步任务的并行组合,如推荐系统和订单处理
异常处理需要在 get() 时捕获异常支持链式调用中的异常处理,如支付状态异常和数据获取错误
异步订单处理适用于简单的一次性订单处理更适用于复杂的订单流程,如折扣计算、优惠券应用和库存校验
推荐系统不适用于多任务结果的合并支持并行任务结果的合并

结论

  • FutureTask:更适用于一次性异步任务,适合单一任务的执行和结果获取,如用户登录、支付状态轮询等。
  • CompletableFuture:适合复杂异步流、任务组合和并行计算场景,如商品详情加载、推荐系统、复杂订单处理和多任务结果合并。

选择适合的工具取决于任务的复杂度和异步处理的需求。

0 0 投票数
Article Rating
订阅评论
提醒
guest
0 评论
最旧
最新 最多投票
内联反馈
查看所有评论
0
希望看到您的想法,请您发表评论x