Java8中引入了不少新的特性,如:
- lambda表达式:Lambda表达式允许在代码中直接定义匿名函数,使得Java对函数式编程的支持更加完善。它极大地简化了代码,使得开发者能够编写出更简洁、更灵活的代码。
- Stream API:Stream API为处理数据提供了一种高效且易于使用的方式。通过使用Stream API,开发者可以编写出更简洁、更易于阅读的代码来处理集合数据。它提供了诸如filter、map、sorted等常用的方法,使得数据操作更加直观和方便。
- Date/Time API:Java 8引入了新的日期和时间API,替代了之前复杂且易出错的java.util.Date和java.util.Calendar类。新的API提供了更加直观和灵活的方式来处理日期和时间。
- 接口的默认方法和静态方法:在Java 8中,接口可以包含默认方法和静态方法。默认方法允许在接口中定义方法的具体实现,而静态方法与类中的静态方法类似,可以在接口中使用static关键字定义。
- 函数式接口:Java 8引入了函数式接口的概念,这是一个只包含一个抽象方法的接口。Lambda表达式可以很容易地与函数式接口结合使用,从而支持函数式编程。
- 方法引用:方法引用是Lambda表达式的一个简化形式,它允许我们直接引用已存在的方法或构造器。
- Optional容器:Optional是一个可以为null的容器对象。它的引入旨在减少空指针异常的发生,使得代码更加健壮。
- CompletableFuture
今天,我们就来看看这个CompletableFuture,它是什么、怎么用、以及使用过程中需要注意什么。
是什么?(CompletableFuture,Future,CompletionStage,Function,Consumer,Supplier...)
CompletableFuture是Java 8中引入的一个功能强大的类,它位于包JUC(java.util.concurrent)下,是Java Future和CompletionStage API的扩展,专为异步编程而设计。它允许我们将任务运行在与主线程分离的其他线程中,并通过回调在主线程中得到异步任务执行的状态,包括是否完成、是否异常等信息。这种设计使得主线程不会阻塞或等待任务的完成,从而可以并行执行其他任务,极大地提高了程序的性能。
CompletableFuture的主要用途在于异步编程,特别是在需要执行耗时的操作(如I/O操作)时,可以将其放在后台线程中执行,同时主线程可以继续执行其他任务。一旦后台任务完成,主线程可以通过回调机制获取结果或处理异常情况(基于观察者设计模式)。
/**
* A {@link Future} that may be explicitly completed (setting its
* value and status), and may be used as a {@link CompletionStage},
* supporting dependent functions and actions that trigger upon its
* completion.
*
* <p>When two or more threads attempt to
* {@link #complete complete},
* {@link #completeExceptionally completeExceptionally}, or
* {@link #cancel cancel}
* a CompletableFuture, only one of them succeeds.
*
* <p>In addition to these and related methods for directly
* manipulating status and results, CompletableFuture implements
* interface {@link CompletionStage} with the following policies: <ul>
*
* <li>Actions supplied for dependent completions of
* <em>non-async</em> methods may be performed by the thread that
* completes the current CompletableFuture, or by any other caller of
* a completion method.</li>
*
* <li>All <em>async</em> methods without an explicit Executor
* argument are performed using the {@link ForkJoinPool#commonPool()}
* (unless it does not support a parallelism level of at least two, in
* which case, a new Thread is created to run each task). To simplify
* monitoring, debugging, and tracking, all generated asynchronous
* tasks are instances of the marker interface {@link
* AsynchronousCompletionTask}. </li>
*
* <li>All CompletionStage methods are implemented independently of
* other public methods, so the behavior of one method is not impacted
* by overrides of others in subclasses. </li> </ul>
*
* <p>CompletableFuture also implements {@link Future} with the following
* policies: <ul>
*
* <li>Since (unlike {@link FutureTask}) this class has no direct
* control over the computation that causes it to be completed,
* cancellation is treated as just another form of exceptional
* completion. Method {@link #cancel cancel} has the same effect as
* {@code completeExceptionally(new CancellationException())}. Method
* {@link #isCompletedExceptionally} can be used to determine if a
* CompletableFuture completed in any exceptional fashion.</li>
*
* <li>In case of exceptional completion with a CompletionException,
* methods {@link #get()} and {@link #get(long, TimeUnit)} throw an
* {@link ExecutionException} with the same cause as held in the
* corresponding CompletionException. To simplify usage in most
* contexts, this class also defines methods {@link #join()} and
* {@link #getNow} that instead throw the CompletionException directly
* in these cases.</li> </ul>
*
* @author Doug Lea
* @since 1.8
*/
public class CompletableFuture<T> implements Future<T>, CompletionStage<T>
从定义中,我们可以看到,CompletableFuture同时实现了Future接口和CompletionStage。
Future接口中定义了如下几个方法:
如:通过get()方法可以获取异步任务的结果,isDone()可以判断任务是否已完成。
CompletionStage
接口在Java中代表一个异步计算的可能阶段。它允许开发者定义当一个或多个CompletionStage
完成时要执行的操作或计算的值。这个接口特别强大,因为它提供了多种方式来组合和编排异步任务。
CompletionStage
接口内部的方法主要由几个关键单词组合而成,如apply
、accept
、run
、then
、both
、either
和async
。每个关键字都对应着一种特定的执行方式或用途:
- apply/combine:这些方法用于在上一阶段执行结束之后,将上一阶段的结果作为指定函数的参数执行函数以产生新的结果。接口参数为
BiFunction
或Function
类型。 - accept:这些方法用于在上一阶段执行结束之后,将上一阶段的结果作为指定操作的参数执行操作,但不会对阶段结果产生影响。接口参数为
BiConsumer
或Consumer
类型。 - run:这些方法定义的操作不依赖于上一阶段的执行结果。只要上一阶段完成(但一般要求正常完成),就会执行指定的操作,且不会对阶段的结果产生影响。接口参数为
Runnable
类型。 - then:这些方法通常用于安排对单个阶段的依赖,并定义当该阶段完成后要执行的操作。
- both/either:这些方法用于处理两个阶段的完成情况。
both
方法会在两个阶段都完成时执行操作,而either
方法则会在任一阶段完成时执行操作。 - async:这个方法关键字表示异步执行,可以在一个
ForkJoinPool
线程池里面,以守护进程的形式执行,或者传入一个Executor
作为任务执行的线程池。
CompletableFuture
是CompletionStage
接口的一个实现,它扩展了Future
接口并增加了异步会点、流式处理以及多个Future
组合处理的能力。这使得Java在处理多任务的协同工作时更加顺畅和便利。
这里,我们再多了解一点BiFunction/Function,BiConsumer/Consumer,Supplier(因为后面的各种方法几乎都会用到相关类型的参数,所以有必要了解一下)
Function:一个函数式接口,R apply(T t)方法,接收一个参数,返回一个结果;
/**
* Represents a function that accepts one argument and produces a result.
*
* <p>This is a <a href="package-summary.html">functional interface</a>
* whose functional method is {@link #apply(Object)}.
*
* @param <T> the type of the input to the function
* @param <R> the type of the result of the function
*
* @since 1.8
*/
@FunctionalInterface
public interface Function<T, R> {
/**
* Applies this function to the given argument.
*
* @param t the function argument
* @return the function result
*/
R apply(T t);
/**
* Returns a composed function that first applies the {@code before}
* function to its input, and then applies this function to the result.
* If evaluation of either function throws an exception, it is relayed to
* the caller of the composed function.
*
* @param <V> the type of input to the {@code before} function, and to the
* composed function
* @param before the function to apply before this function is applied
* @return a composed function that first applies the {@code before}
* function and then applies this function
* @throws NullPointerException if before is null
*
* @see #andThen(Function)
*/
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
/**
* Returns a composed function that first applies this function to
* its input, and then applies the {@code after} function to the result.
* If evaluation of either function throws an exception, it is relayed to
* the caller of the composed function.
*
* @param <V> the type of output of the {@code after} function, and of the
* composed function
* @param after the function to apply after this function is applied
* @return a composed function that first applies this function and then
* applies the {@code after} function
* @throws NullPointerException if after is null
*
* @see #compose(Function)
*/
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
/**
* Returns a function that always returns its input argument.
*
* @param <T> the type of the input and output objects to the function
* @return a function that always returns its input argument
*/
static <T> Function<T, T> identity() {
return t -> t;
}
}
BiFunction:接收两个参数,返回一个结果,是Function的二元化。
/**
* Represents a function that accepts two arguments and produces a result.
* This is the two-arity specialization of {@link Function}.
*
* <p>This is a <a href="package-summary.html">functional interface</a>
* whose functional method is {@link #apply(Object, Object)}.
*
* @param <T> the type of the first argument to the function
* @param <U> the type of the second argument to the function
* @param <R> the type of the result of the function
*
* @see Function
* @since 1.8
*/
@FunctionalInterface
public interface BiFunction<T, U, R> {
/**
* Applies this function to the given arguments.
*
* @param t the first function argument
* @param u the second function argument
* @return the function result
*/
R apply(T t, U u);
/**
* Returns a composed function that first applies this function to
* its input, and then applies the {@code after} function to the result.
* If evaluation of either function throws an exception, it is relayed to
* the caller of the composed function.
*
* @param <V> the type of output of the {@code after} function, and of the
* composed function
* @param after the function to apply after this function is applied
* @return a composed function that first applies this function and then
* applies the {@code after} function
* @throws NullPointerException if after is null
*/
default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t, U u) -> after.apply(apply(t, u));
}
}
Consumer:接收一个参数,无返回值。
/**
* Represents an operation that accepts a single input argument and returns no
* result. Unlike most other functional interfaces, {@code Consumer} is expected
* to operate via side-effects.
*
* <p>This is a <a href="package-summary.html">functional interface</a>
* whose functional method is {@link #accept(Object)}.
*
* @param <T> the type of the input to the operation
*
* @since 1.8
*/
@FunctionalInterface
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
/**
* Returns a composed {@code Consumer} that performs, in sequence, this
* operation followed by the {@code after} operation. If performing either
* operation throws an exception, it is relayed to the caller of the
* composed operation. If performing this operation throws an exception,
* the {@code after} operation will not be performed.
*
* @param after the operation to perform after this operation
* @return a composed {@code Consumer} that performs in sequence this
* operation followed by the {@code after} operation
* @throws NullPointerException if {@code after} is null
*/
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
BiConsumer:接收两个参数,无返回值
/**
* Represents an operation that accepts two input arguments and returns no
* result. This is the two-arity specialization of {@link Consumer}.
* Unlike most other functional interfaces, {@code BiConsumer} is expected
* to operate via side-effects.
*
* <p>This is a <a href="package-summary.html">functional interface</a>
* whose functional method is {@link #accept(Object, Object)}.
*
* @param <T> the type of the first argument to the operation
* @param <U> the type of the second argument to the operation
*
* @see Consumer
* @since 1.8
*/
@FunctionalInterface
public interface BiConsumer<T, U> {
/**
* Performs this operation on the given arguments.
*
* @param t the first input argument
* @param u the second input argument
*/
void accept(T t, U u);
/**
* Returns a composed {@code BiConsumer} that performs, in sequence, this
* operation followed by the {@code after} operation. If performing either
* operation throws an exception, it is relayed to the caller of the
* composed operation. If performing this operation throws an exception,
* the {@code after} operation will not be performed.
*
* @param after the operation to perform after this operation
* @return a composed {@code BiConsumer} that performs in sequence this
* operation followed by the {@code after} operation
* @throws NullPointerException if {@code after} is null
*/
default BiConsumer<T, U> andThen(BiConsumer<? super T, ? super U> after) {
Objects.requireNonNull(after);
return (l, r) -> {
accept(l, r);
after.accept(l, r);
};
}
}
Supplier:无参数,有返回值。
/**
* Represents a supplier of results.
*
* <p>There is no requirement that a new or distinct result be returned each
* time the supplier is invoked.
*
* <p>This is a <a href="package-summary.html">functional interface</a>
* whose functional method is {@link #get()}.
*
* @param <T> the type of results supplied by this supplier
*
* @since 1.8
*/
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}
当然,还有比较熟悉的Runnable:无参数、无返回值。
/**
* The <code>Runnable</code> interface should be implemented by any
* class whose instances are intended to be executed by a thread. The
* class must define a method of no arguments called <code>run</code>.
* <p>
* This interface is designed to provide a common protocol for objects that
* wish to execute code while they are active. For example,
* <code>Runnable</code> is implemented by class <code>Thread</code>.
* Being active simply means that a thread has been started and has not
* yet been stopped.
* <p>
* In addition, <code>Runnable</code> provides the means for a class to be
* active while not subclassing <code>Thread</code>. A class that implements
* <code>Runnable</code> can run without subclassing <code>Thread</code>
* by instantiating a <code>Thread</code> instance and passing itself in
* as the target. In most cases, the <code>Runnable</code> interface should
* be used if you are only planning to override the <code>run()</code>
* method and no other <code>Thread</code> methods.
* This is important because classes should not be subclassed
* unless the programmer intends on modifying or enhancing the fundamental
* behavior of the class.
*
* @author Arthur van Hoff
* @see java.lang.Thread
* @see java.util.concurrent.Callable
* @since JDK1.0
*/
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
好了,了解了这些基本概念之后,我们接下来看看CompletableFuture怎么用。
怎么用?
我们先来直观感受下,CompletableFuture提供了哪些方法
看到这么多的方法,很多很多,眼花缭乱,有没有?
不慌,先来总结一下,总体上有个概念,再做展开。
首先,它肯定提供了创建异步任务的方法,方便我们去快捷的创建一个新的异步任务,这种类型的方法要么是构造方法,要么就是static方法;
其次,它实现了CompletionStage,而CompletionStage中的方法又有几个主要的关键字(run,accept,apply,then,both,async,combine),而这几个关键词又可以按用途分为异步回调类、组合处理类、还有辅助处理类等等这几种类型;
再者,我们结合Funcion/Consumer/Supplier定义,再观察CompletableFuture中的方法,不难看出,*supply*方法参数为Supplier类型:表示无参数、有返回值,*accept*方法参数为Consumer类型:表示有参数、无返回值,*apply*方法参数为Function类型:表示有参数、有返回值,而*run*方法参数为Runnable类型:表示无参数、无返回值。那么,有了这个整体模式上的概念,用起来就更方便了。
还有,很多方法都有一个加了Async,也就是*Async的方法,以及Async方法还有一个多了个Executor类型参数的重载方法,而Async我们从字面意思便可理解为它是和异步有关的,那么,不带Executor类型参数的自然是用了默认的线程池,而带有Executor类型参数的便是指定线程池执行。
All async methods without an explicit Executor argument are performed using the ForkJoinPool.commonPool()
(unless it does not support a parallelism level of at least two, in which case, a new Thread is created to run each task).
To simplify monitoring, debugging, and tracking,
all generated asynchronous tasks are instances of the marker interface CompletableFuture.AsynchronousCompletionTask.
关于默认线程池,在CompletableFuture的javadoc中是这么说明的,在当硬件支持并发的条件下,默认使用的是FockJoinPool.commonPool()公共线程池,而如果硬件本身不支持并发(单核cpu),那么每个任务都会创建一个新线程执行。
好了,有了上面的总结和认识,我们再来从头来看CompletableFuture中提供的方法,主要分如下几类:(也正因为有了上面的总结,所以Async方法,以及带有Executor类型参数的重载方法就不做过多介绍了,再巩固一下,统一理解为:*Async方法,执行任务的并不一定是同一个线程,会用到线程池,而线程池默认为ForkJoinPool.commonPool(),也支持自定义)
创建异步任务:
- supplyAsync:接收一个
Supplier
函数式接口作为参数,异步执行该函数,并返回表示异步计算结果的CompletableFuture
。 - runAsync:接收一个
Runnable
作为参数,异步执行该任务,并返回一个表示异步操作完成通知的CompletableFuture
(无返回值)。
异步回调:
- thenApply:当前任务完成后,执行一个函数并将结果传递给新的
CompletableFuture
。 - thenAccept:当前任务完成后,执行一个接受结果的消费操作,并返回表示该操作的
CompletableFuture
(无返回值)。 - thenRun:当前任务完成后,执行一个无参数的Runnable任务,并返回表示该操作的
CompletableFuture
(无返回值)。 - exceptionally:处理当前
CompletableFuture
计算过程中发生的异常,并返回一个新的CompletableFuture
。 - whenComplete:无论正常完成还是异常完成,都会执行指定的动作。
- handle:与
whenComplete
类似,但允许你根据完成情况返回一个新的结果或抛出异常。
组合处理:
- thenCompose:当前任务完成后,执行一个返回
CompletableFuture
的函数,并返回该函数的结果。 - thenCombine:等待两个
CompletableFuture
都完成,然后应用一个函数将两个结果组合成一个新结果。 - thenAcceptBoth:等待两个
CompletableFuture
都完成,然后应用一个接受两个结果的消费操作。 - runAfterBoth:等待两个
CompletableFuture
都完成,然后执行一个Runnable任务。 - applyToEither:等待两个
CompletableFuture
中的任一个完成,然后应用一个函数到该结果。 - acceptEither:等待两个
CompletableFuture
中的任一个完成,然后应用一个接受结果的消费操作。 - runAfterEither:等待两个
CompletableFuture
中的任一个完成,然后执行一个Runnable任务。 - allOf/anyOf:allOf返回的CompletableFuture是多个任务都执行完成后才会执行,只要有一个任务执行异常,则返回的CompletableFuture执行get方法时会抛出异常,如果都是正常执行,则get返回null。
代码示例:
以下是一个简单的使用示例,展示CompletableFuture
的几种方法:
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class CompletableFutureDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建异步任务
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000); // 模拟耗时任务
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
return "Hello, World!";
});
// 异步回调
future.thenAccept(System.out::println);
future.thenRun(() -> System.out.println("Task completed!"));
// 组合处理
CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> 10);
CompletableFuture<Integer> future3 = CompletableFuture.supplyAsync(() -> 20);
CompletableFuture<Integer> sumFuture = future2.thenCombine(future3, (a, b) -> a + b);
System.out.println("Sum: " + sumFuture.get());
// 取消任务(如果尚未开始)
// future.cancel(true);
}
}
异常处理:
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
throw new RuntimeException("Error during computation");
});
CompletableFuture<String> exceptionHandledFuture = future.exceptionally(e -> {
System.err.println("Caught exception: " + e.getMessage());
return "Default value";
});
String result = exceptionHandledFuture.get();
System.out.println(result); // 输出:Default value
allOf:
// 创建异步执行任务:
CompletableFuture<Double> cf = CompletableFuture.supplyAsync(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
return 1.0;
});
CompletableFuture<Double> cf2 = CompletableFuture.supplyAsync(()->{
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
}
return 2.0;
});
CompletableFuture<Double> cf3 = CompletableFuture.supplyAsync(()->{
try {
Thread.sleep(1300);
} catch (InterruptedException e) {
}
return 3.0;
});
//allof等待所有任务执行完成才执行,如果有一个任务异常终止,则cf4.get时会抛出异常,如果都正常执行,cf4.get返回null
//anyOf是只要有一个任务执行完成,无论是正常执行或者执行异常,都会执行cf4,cf4.get的结果就是已执行完成的任务的执行结果
CompletableFuture cf4=CompletableFuture.allOf(cf,cf2,cf3).whenComplete((a,b)->{
if(b!=null){
System.out.println("error stack trace->");
b.printStackTrace();
}else{
System.out.println("run succ,result->"+a);
}
});
//等待任务执行完成
System.out.println("cf4 run result->"+cf4.get());
以及普通的join方法:(别忘了,它还实现了Future接口)
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
// 模拟耗时任务
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
return "Result of asynchronous computation";
});
// 使用 join 方法获取结果,如果结果还未准备好,会阻塞当前线程
String result = future.join(); // 阻塞等待异步任务完成,然后返回结果
System.out.println(result); // 输出:Result of asynchronous computation
现在,知道是CompletableFuture是什么,也知道了它怎么用了。
最后,再看看使用时需要注意什么吧。
使用CompletableFuture需要注意的地方
- 线程池的选择:默认情况下,CompletableFuture会使用公共的ForkJoinPool线程池。但是,如果所有CompletableFuture共享一个线程池,并且某些任务执行较慢的I/O操作,可能会导致线程池中所有线程都阻塞在I/O操作上,从而造成线程饥饿。因此,建议根据不同的业务类型创建不同的线程池。
- 异常处理:在使用CompletableFuture时,需要注意异常处理。如果在异步任务中抛出异常,而后续操作没有正确处理这些异常,可能会导致程序出现不可预料的行为。例如,前面提到的代码片段中,如果在supplyAsync中抛出的异常没有被正确传播到后续操作中,那么调用result.join()时可能不会抛出预期的异常。
- 结果获取:虽然CompletableFuture提供了非阻塞的方式来获取异步任务的结果(如通过回调函数),但在某些情况下,你可能需要阻塞等待结果。这时,可以使用CompletableFuture的get方法。但请注意,get方法会阻塞调用线程,直到异步任务完成或超时。
总的来说,CompletableFuture为Java的异步编程提供了强大的支持,但使用时也需要注意线程池的选择、异常处理以及结果获取等方面的问题。
好了,有了这些,基本也够应用日常工作使用了,多学习、多练习、多使用、多踩坑,才能不断提升、进步。
最后的最后,建议学习一个框架,vertx,有了CompletaleFuture基础,学习起来应该会更容易一些,可以试试看,多交流。