
并发编程之:异步调用获取返回值
大家好,我是小黑,一个在互联网苟且偷生的农民工。
Runnable
在创建线程时,可以通过new Thread(Runnable)
方式,将任务代码封装在Runnable
的run()
方法中,将Runnable
作为任务提交给Thread
,或者使用线程池的execute(Runnable)
方法处理。
Runnable的问题
如果你之前有看过或者写过Runnable相关的代码,肯定会看到有说Runnable不能获取任务执行结果的说法,这就是Runnable存在的问题,那么可不可以改造一下来满足使用Runnable并获取到任务的执行结果呢?答案是可以的,但是会比较麻烦。
首先我们不能修改run()
方法让它有返回值,这违背了接口实现的原则;我们可以通过如下三步完成:
- 我们可以在自定义的
Runnable
中定义变量,存储计算结果; - 对外提供方法,让外部可以通过方法获取到结果;
- 在任务执行结束之前如果外部要获取结果,则进行阻塞;
如果你有看过我之前的文章,相信要做到功能并不复杂,具体实现可以看我下面的代码。
从运行结果我们可以看出,确实能够在主线程中获取到Runnable的返回结果。
以上代码看似从功能上可以满足了我们的要求,但是存在很多并发情况的问题,实际开发中极不建议使用。在我们实际的工作场景中这样的情况非常多,我们不能每次都这样自定义搞一套,并且很容易出错,造成线程安全问题,那么在JDK
中已经给我们提供了专门的API来满足我们的要求,它就是Callable。
Callable
我们通过Callable来完成我们上面说的1-1亿的累加功能。
运行结果:
可以在创建线程时将Callable
对象封装在FutureTask
对象中,交给Thread
对象执行。
FutureTask
之所以可以作为Thread
创建的参数,是因为FutureTask
是Runnable
接口的一个实现类。
既然FutureTask也是Runnable接口的实现类,那一定也有run()方法,我们来通过源码看一下是怎么做到有返回值的。
首先在FutureTask中有如下这些信息。
在这个方法中的核心逻辑就是执行callable的call()方法,将结果赋值,如果有异常则封装异常。
然后我们看一下get方法如何获取结果的。
在FutureTask中除了get()
方法还提供有一些其他方法。
- get(timeout,unit):获取结果,但只等待指定的时间;
- cancel(boolean mayInterruptIfRunning):取消当前任务;
- isDone():判断任务是否已完成。
CompletableFuture
在使用FutureTask来完成异步任务,通过get()方法获取结果时,会让获取结果的线程进入阻塞等待,这种方式并不是最理想的状态。
在JDK8
中引入了CompletableFuture
,对Future进行了改进,可以在定义CompletableFuture
传入回调对象,任务在完成或者异常时,自动回调。
以上代码可以通过lambda表达式进行简化。
通过示例我们发现CompletableFuture
的优点:
- 异步任务结束时,会自动回调某个对象的方法;
- 异步任务出错时,会自动回调某个对象的方法;
- 主线程设置好回调后,不再关心异步任务的执行。
当然这些优点还不足以体现CompletableFuture的强大,还有更厉害的功能。
串行执行
多个CompletableFuture
可以串行执行,如第一个任务先进行查询,第二个任务再进行更新
并行执行
CompletableFuture除了可以串行,还支持并行处理。
通过anyOf()
可以实现多个任务只有一个成功,CompletableFuture
还有一个allOf()
方法实现了多个任务必须都成功之后的合并任务。
小结
Runnable接口实现的异步线程默认不能返回任务运行的结果,当然可以通过改造实现返回,但是复杂度高,不适合进行改造;
Callable接口配合FutureTask可以满足异步任务结果的返回,但是存在一个问题,主线程在获取不到结果时会阻塞等待;
CompletableFuture进行了增强,只需要指定任务执行结束或异常时的回调对象,在结束后会自动执行,并且支持任务的串行,并行和多个任务都执行完毕后再执行等高级方法。
文章转载自公众号:小黑说java
