Future、CompletionService、CompletableFuture介绍与对比

Future

1、基本介绍

Future是JDK1.5 提供的接口,是用来以阻塞的方式获取线程异步执行完的结果。

FutureTask 类是 Java 中 Future 接口的一个实现,同时也实现了 Runnable 接口。它用于表示异步计算的结果,允许一个任务在一个线程中计算结果,在另一个线程中获取计算的结果。

import java.util.concurrent.*;

public class CallableWithThreadPoolExample {

    public static void main(String[] args) {
        // 创建一个固定大小的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(2);

        // 创建一个Callable任务
        Callable<Integer> callableTask = () -> {
            // 模拟耗时的计算
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 返回计算结果
            return 42;
        };

        // 提交Callable任务给线程池执行
        Future<Integer> future = executorService.submit(callableTask);

        // 执行其他任务,不会阻塞

        try {
            // 获取Callable任务的结果,会阻塞直到结果准备好
            Integer result = future.get();

            System.out.println("Result: " + result);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        } finally {
            // 关闭线程池
            executorService.shutdown();
        }
    }
}

2、按照提交任务的顺序获取执行结果

FutureTask 提供了 get 方法,可以用于获取异步计算的结果。然而,FutureTask 本身并没有保证按照任务提交的顺序返回结果。

如果你需要按照任务提交的顺序获取执行结果,你可以使用 ExecutorService 的 invokeAll 方法提交一批 Callable 任务,并得到一组 Future 对象,然后可以通过迭代这组 Future 对象来获取执行结果,迭代的顺序即为任务提交的顺序。

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

public class OrderPreservingExample {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(5);

        List<Callable<String>> tasks = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            final int taskId = i;
            Callable<String> task = () -> {
                // Simulate some computation
                Thread.sleep(1000);
                return "Task " + taskId + " completed";
            };
            tasks.add(task);
        }

        try {
            List<Future<String>> results = executorService.invokeAll(tasks);

            for (Future<String> result : results) {
                System.out.println(result.get()); // Results are obtained in the order of task submission
            }
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        } finally {
            executorService.shutdown();
        }
    }
}

在上面的示例中,invokeAll 方法会按照任务列表的顺序返回 Future 对象的列表,因此通过迭代这个列表,可以按照任务提交的顺序获取执行结果。请注意,invokeAll 方法会阻塞直到所有任务完成。

CompletionService

1、介绍

CompletionService 是 Java 中用于处理一批异步任务的工具类,它允许你以异步的方式提交任务,并在任务完成时按照完成顺序获取结果。CompletionService 的底层原理主要基于阻塞队列和线程池。

CompletionService 使用一个阻塞队列来保存已完成的任务。当一个任务完成时,它会被放入队列中。阻塞队列的选择通常是 LinkedBlockingQueue,它是一个先进先出的队列,确保按照任务完成的顺序排列。

CompletionService 通常与 Executor 框架一起使用。创建一个 ExecutorService,并将其传递给 CompletionService 的构造函数。这个线程池负责执行提交的任务。

当想要获取已完成的任务的结果时,可以调用 CompletionService 的 take() 或 poll() 方法。这些方法会从阻塞队列中取出已完成的任务的 Future,并返回它。如果队列为空,take() 方法会阻塞,而 poll() 方法会返回 null。

由于使用了阻塞队列,你可以确保按照任务完成的顺序获取结果,即使任务的完成顺序与它们被提交的顺序不同。

2、按照任务完成的先后顺序获取结果

Future 接口本身并没有直接提供按照任务提交的顺序获取执行结果的机制。当通过 ExecutorService 提交多个任务时,Future 对象的完成顺序可能不一定与任务的提交顺序完全一致。

ExecutorCompletionService 是 ExecutorService 的一个实现,它可以将已完成的任务放入一个阻塞队列中,然后可以通过检索队列的元素来按照任务完成的顺序获取结果。

当向 CompletionService 提交一个任务时,它会将该任务包装在一个 Future 中,并将这个 Future 放入阻塞队列中。

import java.util.concurrent.*;

public class OrderPreservingFutureExample {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        ExecutorCompletionService<String> completionService = new ExecutorCompletionService<>(executorService);

        for (int i = 0; i < 10; i++) {
            final int taskId = i;
            Callable<String> task = () -> {
                // Simulate some computation
                Thread.sleep(1000);
                return "Task " + taskId + " completed";
            };

            completionService.submit(task);
        }

        try {
            for (int i = 0; i < 10; i++) {
                Future<String> result = completionService.take(); // Blocking until a task is completed
                System.out.println(result.get()); // Results are obtained in the order of completion
            }
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        } finally {
            executorService.shutdown();
        }
    }
}

在上面的示例中,ExecutorCompletionService 的 take 方法会阻塞直到有任务完成,然后返回一个 Future 对象,我们能够通过这个对象获取任务的执行结果。通过循环迭代 take 方法,可以按照任务完成的顺序获取执行结果。

这是因为CompletionService 的 take 方法是阻塞的。当调用 take 方法时,它会等待至少一个任务完成并返回一个包含已完成任务结果的 Future 对象。如果当前没有已完成的任务,take 方法会阻塞直到有任务完成为止。这种阻塞行为使得能够按照任务完成的顺序获取结果,因为它会返回最先完成的任务的结果。

如果我们希望非阻塞地检查是否有任务完成,可以使用 poll 方法,它会立即返回一个 Future 对象,如果没有已完成的任务,则返回 null。但使用 poll 方法可能需要在循环中进行主动轮询。

import java.util.concurrent.*;

public class PollingCompletionServiceExample {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        CompletionService<String> completionService = new ExecutorCompletionService<>(executorService);

        // 提交任务到CompletionService
        for (int i = 0; i < 5; i++) {
            final int taskId = i;
            Callable<String> task = () -> {
                // 模拟一些计算
                Thread.sleep(1000);
                return "Task " + taskId + " completed";
            };

            completionService.submit(task);
        }

        // 轮询任务的完成状态
        for (int i = 0; i < 5; i++) {
            Future<String> result = completionService.poll();
            if (result != null) {
                // 任务已完成,处理结果
                try {
                    System.out.println(result.get());
                } catch (InterruptedException | ExecutionException e) {
                    e.printStackTrace();
                }
            } else {
                // 没有已完成的任务
                System.out.println("No completed tasks at the moment.");
            }
        }

        executorService.shutdown();
    }
}

在上面的示例中,poll方法被用于轮询已完成的任务。如果有任务已经完成,它会返回一个包含任务结果的Future对象;否则,返回null。需要注意的是,如果任务尚未完成,poll方法会立即返回null,因此你可能需要在循环中添加适当的延迟,以避免过于频繁地检查任务状态。

CompletableFuture

1、介绍

Future和CompletionService 在实际使用过程中存在一些局限性比如不支持异步任务的编排组合、获取计算结果的 get() /take()为阻塞调用。

Java 8 才被引入CompletableFuture 类可以解决Future 的这些缺陷。CompletableFuture 除了提供了更为好用和强大的 Future 特性之外,还提供了函数式编程、异步任务编排组合(可以将多个异步任务串联起来,组成一个完整的链式调用)等能力。

CompletableFuture 同时实现了 Future 和 CompletionStage 接口。

CompletionStage 接口描述了一个异步计算的阶段。很多计算可以分成多个阶段或步骤,此时可以通过它将所有步骤组合起来,形成异步计算的流水线。

CompletableFuture 除了提供了更为好用和强大的 Future 特性之外,还提供了函数式编程的能力。

2、CompletableFuture怎么非阻塞的获取任务结果

通过 thenApply, thenAccept, 或者 thenRun 方法,注册回调函数,这些函数会在 CompletableFuture 完成时被异步调用。这样,处理任务的结果而不必阻塞当前线程。

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello");

future.thenApply(result -> {
    // Non-blocking callback to process the result
    System.out.println("Received result: " + result);
    return result.toUpperCase();
});

// Continue with other non-blocking operations

使用 thenCombine, thenAcceptBoth, runAfterBoth, applyToEither, acceptEither, 等方法,将多个 CompletableFuture 的结果组合在一起,而不必阻塞等待每个任务的完成。


CompletableFuture<String> firstTask = CompletableFuture.supplyAsync(() -> {
    // Simulate some computation
    return "First Task";
});

CompletableFuture<String> secondTask = CompletableFuture.supplyAsync(() -> {
    // Simulate some computation
    return "Second Task";
});

CompletableFuture<String> thirdTask = CompletableFuture.supplyAsync(() -> {
    // Simulate some computation
    return "Third Task";
});

// 使用thenCompose确保任务按照顺序完成
CompletableFuture<String> result = firstTask.thenCompose(result1 ->
        secondTask.thenCompose(result2 ->
                thirdTask.thenApply(result3 -> result1 + " -> " + result2 + " -> " + result3)
        )
);

// 异步获取结果
result.thenAcceptAsync(System.out::println);

// 阻塞等待所有任务完成
result.join();

3、CompletableFuture异常处理

上面我们提到,firstTask的结果能传递给secondTask,secondTask的结果能传递给thirdTask,那么中途有个任务突然出现问题怎么办呢。

我们在firstTask 中抛出异常,并对其进行处理:

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    throw new RuntimeException();
})
        .exceptionally(ex -> "errorFirstTask")
        .thenApply(firstTask -> firstTask + "secondTask")
        .thenApply(secondTask -> secondTask + "thirdTask")
        .thenApply(thirdTask -> thirdTask + "lastTask");

firstTask 抛出异常,然后通过 exceptionally() 方法处理了异常,并返回新的结果,这个新的结果将传递给secondTask。最终的输出结果是errorFirstTask secondTask thirdTask lastTask

除此之外,还可以使用 handle(BiFunction fn) 来处理异常。

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "resultA")
        .thenApply(firstTask -> firstTask + "secondTask")
        .thenApply(secondTask -> {throw new RuntimeException();})
        .handle(new BiFunction<Object, Throwable, Object>() {
            @Override
            public Object apply(Object re, Throwable throwable) {
                if (throwable != null) {
                    return "errorThirdTask ";
                }
                return re;
            }
        })
        .thenApply(thirdTask -> thirdTask + "lastTask");
`CompletionService` 和 `CompletableFuture` 都是 Java 中处理并发编程中异步任务完成情况的重要工具,它们分别对应于 `ExecutorService` 提供的 `Future` 接口以及 Java 8 引入的更高级别 API。 `CompletionService` 是 `ExecutorService` 的一个辅助类,它允许你在提交给线程池的任务完成后获取结果,通过将 `Future` 对象放入队列的方式实现。当你需要同步地等待并获取多个独立任务的结果时,`CompletionService` 尤其有用。例如,在批量查询数据库、文件I/O操作或者其他耗时操作时,可以方便地管理所有任务的完成顺序。 `CompletableFuture` 则是一个强大的功能丰富的类,它提供了一种链式调用的方式来处理异步计算。它的设计思路是基于函数式编程,支持多种操作,如 `thenApply`, `thenAccept`, `thenCombine` 等,允许你在异步任务之间进行复杂的控制流操作。`CompletableFuture` 更适合于那些需要对结果进行进一步处理或者有复杂依赖关系的任务。 使用场景举例: 1. **CompletionService**:当你需要在一组任务完成后按照某种顺序依次处理结果,比如批量读取文件并将结果保存到数据库,这时就需要用到 `CompletionService` 来跟踪每个任务的完成,并保证按预期顺序处理。 2. **CompletableFuture**:如果你正在构建一个复杂的分布式系统或者微服务架构,其中涉及到大量依赖性和异步操作,`CompletableFuture` 可以帮助你在链式调用中管理这些操作,保持代码的清晰和简洁。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值