Java的异步编程是一项非常常用的多线程技术。
之前通过源码详细分析了ThreadPoolExecutor《你真的懂ThreadPoolExecutor线程池技术吗?看了源码你会有全新的认识》。通过创建一个ThreadPoolExecutor,往里面丢任务就可以实现多线程异步执行了。
但之前的任务主要倾向于线程池,并没有讲到异步编程方面的内容。本文将通过介绍Executor+Future框架(FutureTask是实现的核心),来深入了解下Java的异步编程。
万事从示例开始,我们先通过示例Demo有一个直观的印象,再深入去了解概念与原理。
使用示例
Demo:
使用上比较简单,
运行结果:
任务1异步执行:0 任务2异步执行:0 任务2异步执行:1 ... 任务2异步执行:45 同步代码 任务2异步执行:24 ... 任务1异步执行:199 任务1:执行完成 ... 任务2异步执行:199 任务2:执行完成 假若你多次执行这个程序,会发现结果大大的不一样,因为两个任务和同步代码是异步由多条线程执行的,打印的结果当然是随机的。
回顾这个Demo做了什么,
- 构建了一个线程池
 - 往线程池里面丢两个需要执行的任务
 - 最后获取这两个任务的结果
 
其中第二点是异步执行两个任务,这两个任务和主线程分别是用了三个线程并发执行的,第三点是在主线程中同步等待两个任务的结果。
很容易看出来,异步编程的好处就在于可以让不相干的任务异步执行,不阻塞主线程。若是主线程需要异步执行的结果,此时再去等待结果会更加高效,提高程序的执行效率。
下面来看看整个流程的实现原理。
源码分析
一般在实际项目中,都会有配置有自己的线程池,建议大家在用异步编程时,配置一个专用的线程池,做好线程隔离,避免异步线程影响到其他模块的工作。Demo中为了方便,直接调用Exectors的方法生成一个临时的线程池,日常不建议使用。
我们从这个ExecutorService.submit()方法入手,看看整体实现。

ExecutorService.submit()定义一个接口。这个接口接收一个Callable参数(执行的任务),返回一个Future(计算结果)。
Callable,相当于一个需要执行的任务。它不接收任何参数,可以返回结果,可以抛出异常。相类似的还有Runnable,它也是不接收,不同点在于它不返回结果,也不抛异常,异常需要在任务内部处理。总结来说Callable更像一个方法的调用,Runnable则是一个不需要理会结果的调用。在JDK 8以后,它们都可以通过Lamda表达式写法去替代内部类的写法(详见Demo)。
Future,一个异步计算的结果。调用get()方法可以得到对应的计算结果,如果调用时没有异步计算完,会阻塞等待计算的结果。同时它还提供方法可以尝试取消任务的执行。
看回ExecutorService.submit()的实现,代码在实现类AbstractExecutorService中。
除了它接口的实现,还提供了两种变形。原来接口只接收Callable参数,实现类中还新增了接收Runnable参数的。
如果看过之前写的
因为FutureTask有run()接口,所以可以直接用一个Callable/Runnable创建一个FutureTask单独执行。但这样并没有异步的效果,因为没有启用新的线程去跑,而是在原来的线程阻塞执行的。
到这里我们清楚知道了,submit()方法重点是利用Callable/Runnable创建一个FutureTask,然后多线程执行run()方法,达到异步处理并且得到结果的效果。而
                        
                        
                    
