Callable
有了Runnable,为什么还要Callable?
我们先来看下Callable的接口:
1 | java复制代码public interface Callable<V> { |
第一点是不能返回值,对于 Runnable 而言,它不能返回一个返回值,虽然可以利用其他的一些办法,比如在 Runnable 方法中写入日志文件或者修改某个共享的对象的办法,来达到保存线程执行结果的目的,但这种解决问题的行为千曲百折,属于曲线救国,效率着实不高。
实际上,在很多情况下执行一个线程时,我们都希望能得到执行的任务的结果,也就是说,我们是需要得到返回值的,比如请求网络、查询数据库等。我们看接口中的V就代表返回值。
第二点是不能抛异常,我们看下Callable接口定义的时候throw了Exception,而 Runnable是没有的,Runnable只能这样写,在里面try catch掉:
1 | java复制代码Runnable runnable = new Runnable() { |
最后对比一下Runnable和Callable
- 方法名,Callable 规定的执行方法是 call(),而 Runnable 规定的执行方法是 run();
- 返回值,Callable 的任务执行后有返回值,而 Runnable 的任务执行后是没有返回值的;
- 抛出异常,call() 方法可抛出异常,而 run() 方法是不能抛出受检查异常的;
- 和 Callable 配合的有一个 Future 类,通过 Future 可以了解任务执行情况,或者取消任务的执行,还可获取任务执行的结果,这些功能都是 Runnable 做不到的,Callable 的功能要比 Runnable 强大。
Future
下面就来介绍一下Future,上面说到Callable是可以返回值的,那这个返回值怎么拿呢?就是通过 Future 类的 get 方法来获取 。
因此,Future 就相当于一个存储器,它存储了 Callable 的 call 方法的任务结果。除此之外,我们还可以通过 Future 的 isDone 方法来判断任务是否已经执行完毕了,还可以通过 cancel 方法取消这个任务,或限时获取任务的结果等,总之 Future 的功能比较丰富。
如何创建Future
一种是通过线程池,之前在讲线程池的时候也提到过, 《线程池源码精讲》
1 | java复制代码 ExecutorService service = Executors.newFixedThreadPool(10); |
还有一种是通过FutureTask创建
1 | java复制代码 FutureTask<Integer> integerFutureTask = new FutureTask<>(new CallableTask()); |
有了宏观上的认识,我们来看下Future里面的方法:
1 | java复制代码public interface Future<V> { |
get() 方法
- 最常见的就是当执行 get 的时候,任务已经执行完毕了,可以立刻返回,获取到任务执行的结果。
- 任务还没有结果,如果任务还没开始或在进行中,我们去调用 get 的时候,都会把当前的线程阻塞,直到任务完成再把结果返回回来。
- 任务执行过程中抛出异常,一旦这样,我们再去调用 get 的时候,就会抛出 ExecutionException 异常,不管我们执行 call 方法时里面抛出的异常类型是什么,在执行 get 方法时所获得的异常都是 ExecutionException。
- 任务被取消了,如果任务被取消,我们用 get 方法去获取结果时则会抛出 CancellationException。
- 任务超时, get 方法有一个重载方法,就是带延迟参数的,调用了这个带延迟参数的 get 方法之后,如果 call 方法在规定时间内正常顺利完成了任务,那么 get 会正常返回;但是如果到达了指定时间依然没有完成任务,get 方法则会抛出 TimeoutException,代表超时了。
isDone() 方法
该方法是用来判断当前这个任务是否执行完毕了。需要注意的是,这个方法如果返回 true,则代表执行完成了,如果返回 false 则代表还没完成。
但这里如果返回 true,并不代表这个任务是成功执行的,比如说任务执行到一半抛出了异常。那么在这种情况下,对于这个 isDone 方法而言,它其实也是会返回 true 的,因为对它来说,虽然有异常发生了,但是这个任务在未来也不会再被执行,它确实已经执行完毕了。所以 isDone 方法在返回 true 的时候,不代表这个任务是成功执行的,只代表它执行完毕了。
cancel()方法
- 当任务还没有开始执行时,一旦调用 cancel,这个任务就会被正常取消,未来也不会被执行,那么 cancel 方法返回 true。
- 如果任务已经完成,或者之前已经被取消过了,那么执行 cancel 方法则代表取消失败,返回 false。因为任务无论是已完成还是已经被取消过了,都不能再被取消了。
- 当这个任务正在执行,这个时候执行 cancel 方法是不会直接取消这个任务的,而是会根据我们传入的参数做判断。cancel 方法是必须传入一个参数,该参数叫作 mayInterruptIfRunning,它是什么含义呢?如果传入的参数是 true,执行任务的线程就会收到一个中断的信号,正在执行的任务可能会有一些处理中断的逻辑,进而停止,这个比较好理解。如果传入的是 false 则代表不中断正在运行的任务,也就是说,本次 cancel 不会有任何效果,同时 cancel 方法会返回 false。
isCancelled() 方法
判断是否被取消,它和 cancel 方法配合使用,比较简单。
下面看下FutureTask的类图:
我们看了上面的代码其实也能猜到,既然 futureTask 能丢到 Thread 类里面去执行,那它肯定继承了Runnable接口,实现了run方法;既然能够调用get()方法,肯定是继承了Future接口,与上面的类图吻合。
我们看下源码,看下run()方法,很简单,里面执行的逻辑就是Callable里面的call方法,最终将计算出来的结果保存到outcome里面去,然后唤醒阻塞的线程。
看下get()方法,很简单,如果任务结束完成了,直接把outcome里的值返回,否则加入到阻塞队列,类似于AQS。《ReentrantLock介绍及AQS源码精讲》
最后看下流程图:
CompletableFuture
上面介绍了Future/Callable的使用和原理,下面介绍下CompletableFuture。
CompletableFuture对Future做了改进,主要是在get()方法上,主线程如果需要依赖该任务执行结果继续后续操作时,不再需要等待,而是可以直接传入一个回调对象,当异步任务执行完成后,自动调用该回调对象,相当于实现了异步回调通知功能。
除此之外,CompletableFuture还提供了非常强大的功能,比如对于回调对象的执行,可以放到非任务线程中执行,也能用任务线程执行;提供了函数式编程能力,简化了异步编程的复杂性;提供了多个CompletableFuture的组合与转化功能。
看下类图,实现了CompletionStage和Future接口。
Future就是上面讲的Future,里面有5个方法。CompletionStage表示任务执行的一个阶段,每个异步任务都会返回一个新的CompletionStage对象,我们可以针对多个CompletionStage对象进行串行、并行或者聚合的方式来进行后续下一阶段的操作,简单来说,就是实现异步任务执行后的自动回调功能。
CompletableFuture的构建
CompletableFuture提供了四个静态方法来构建一个异步事件,方法如下。
- supplyAsync(Supplier supplier):带有返回值的异步执行方法,传入一个函数式接口,返回一个新的CompletableFuture对象。默认使用ForkJoinPool.commonPool()作为线程池执行异步任务。
- supplyAsync(Supplier supplier,Executor executor):带有返回值的异步执行方法,多了一个Executor参数,表示使用自定义线程池来执行任务。
- runAsync(Runnable runnable):不带返回值的异步执行方法,传入一个Runnable,返回一个新的CompletableFuture对象。默认使用ForkjoinPool.commonPool()作为线程池执行异步任务。
- runAsync(Runnable runnable,Executor executor):不带返回值的异步执行方法, 多了一个Executor参数,表示使用自定义线程池来执行任务。
下面看下CompletableFuture的简单用法:
1 | java复制代码public static void main(String[] args) throws ExecutionException, InterruptedException { |
cf1就是执行一个任务,用的是默认ForkJoinPool的线程池,不带返回值,cf1.get()是阻塞获取值,因为不带返回值,所以获取的是null。
cf2是执行一个带返回值的任务,里面就干一件事return hello world,此时主线程可以继续往下执行做其他事情,待任务执行完以后,thenAccept方法接收到返回的hello world,然后打印出来。
CompletableFuture方法介绍
我们可以看下CompletableFuture类里面有38个方法,十分的多,下面和大家分类介绍一下。
获取结果方法
CompletableFuture类实现了Future接口,所以它开始可以像Future那样主动通过阻塞或者轮询的方式来获得执行结果。
- get(),基于阻塞的方式获取异步任务执行结果。
- get (long timeout, TimeUnit unit),通过带有超时时间的阻塞方式获取异步执行结果。
- join(),和 get() 方法的作用相同,唯一不同的点在于 get() 方法允许被中断,也就是会抛出InterruptedException ,但是join()不允许被中断。
- getNow(T valueIfAbsent),这个方法有点特殊,如果当前任务已经执行完成,则返回执行结果,否则返回传递进去的参数 valueIfAbsent 。
在CompletableFuture类中还有一个比较有意思的方法 complete(T value) ,它表示完成完成计算,
也就是把 value 设置为CompletableFuture的返回值并且唤醒在上述方法阻塞的线程。
我们看下下面的例子,就是创建两个线程t1和t2,线程里面通过completableFuture.get()方法阻塞,当我们调用cf.complete(“Finish”)方法的时候,相当于往里面赋值了,get()方法取到值了,才能继续往下走。
1 | java复制代码public class CompleteExample { |
纯消费类型的方法
纯消费类型的方法,指依赖上一个异步任务的结果作为当前函数的参数进行下一步计算,它的特点是不返回新的计算值,这类的方法都包含 Accept
这个关键字。在CompletionStage中包含9个Accept关键字的方法,这9个方法又可以分为三类:依赖单个CompletionStage任务完成,依赖两个CompletionStage任务都完成,依赖两个CompletionStage中的任何一个完成。
1 | java复制代码//当前线程同步执行 |
thenAccept上面演示过了,下面演示下thenAcceptBoth() 方法,当task1和task2都返回值以后,然后再一起打印出来。
1 | java复制代码public class AcceptExample { |
有返回值类型的方法
有返回值类型的方法,就是用上一个异步任务的执行结果进行下一步计算,并且会产生一个新的有返回值的CompletionStage对象。
在CompletionStage中,定义了9个带有返回结果的方法,同样也可以分为三个类型:依赖单个CompletionStage任务完成,依赖两个CompletionStage任务都完成,依赖两个CompletionStage中的任何一个完成。
1 | java复制代码public <U> CompletionStage<U> thenApply(Function<? super T,? extends U> fn); |
thenApply() 方法
这新建一个任务return hello,thenApply在拿到值以后再和world拼接,然后再返回值,然后通过get获取到值。
1 | java复制代码public class ApplyExample { |
thenCombineAsync() 方法
thenCombineAsync的作用就是将task1和task2的值都拿到以后返回值。
1 | java复制代码public class CombineDemo { |
不消费也不返回的方法
也是9个方法
1 | java复制代码public CompletionStage<Void> thenRun(Runnable action); |
这里新建两个任务,一个是return Both,一个是return Message,在都执行结束以后,因为run是不消费也不返回的,所以入参为0,不需要你们的参数,也不返回,所以没有return。
1 | java复制代码public class RunExample { |
多任务组合
1 | java复制代码public <U> CompletionStage<U> thenCompose(Function<? super T, ? extends |
异常处理
异常处理一共三个方法
- whenComplete:表示当任务执行完成后,会触发的方法,它的特点是,不论前置的CompletionStage任务是正常执行结束还是出现异常,都能够触发特定的 action 方法。
- handle:表示前置任务执行完成后,不管前置任务执行状态是正常还是异常,都会执行handle中的fn 函数,它和whenComplete的作用几乎一致,不同点在于,handle是一个有返回值类型的方法。
- exceptionally:接受一个 fn 函数,当上一个CompletionStage出现异常时,会把该异常作为参数传递到 fn 函数。
这里写在一个例子里面,具体用哪种类型,小伙伴按照具体场景具体选取。
最后CompletableFuture里面的方法十分的多,本文介绍了几个,抛砖引玉,更多的是小伙伴在实际开发过程中慢慢的用,熟能生巧,有些方法缺少应用场景也很难举出例子来,以及这些方法里面传的参数都是函数式接口,java8新特性lambda表达式,这个也是需要学会的,否则会看不懂。感谢收看~
本文转载自: 掘金