一.什么是异步?为什么要用它?
异步编程提供了一个非阻塞的,事件驱动的编程模型。 这种编程模型利用系统中多核执行任务来提供并行,因此提供了应用的吞吐率。此处吞吐率是
指在单位时间内所做任务的数量。 在这种编程方式下, 一个工作单元将独立于主应用线程而执行, 并且会将它的状态通知调用线程:成功,处理中或者失败。
我们需要异步来消除阻塞模型。其实异步编程模型可以使用同样的线程来处理多个请求, 这些请求不会阻塞这个线程。
想象一个应用正在使用的线程正在执行任务,
然后等待任务完成才进行下一步。
log框架就是一个很好的例子:典型地你想将异常和错误日志记录到一个目标中, 比如文件,数据库或者其它类似地方。
你不会让你的程序等待日志写完才执行,否则程序的响应就会受到影响。
相反,如果对log框架的调用是异步地,应用就可以并发执行其它任务而无需等待。这是一个非阻塞执行的例子。
二.异步编程
考虑有一个耗时的操作,操作完后会返回一个结果(不管是正常结果还是异常),程序如果想拥有比较好的性能不可能由线程去等待操作的完成,而是应该采用listener模式。jdk并发包里的Future代表了未来的某个结果,当我们向线程池中提交任务的时候会返回该对象。代码例子:
- /**
- * jdk1.8之前的Future
- *
- * @author Administrator
- *
- */
- public class JavaFuture {
- public static void main(String[] args) throws Throwable, ExecutionException {
- ExecutorService executor = Executors.newFixedThreadPool(1);
- // Future代表了线程执行完以后的结果,可以通过future获得执行的结果
- // 但是jdk1.8之前的Future有点鸡肋,并不能实现真正的异步,需要阻塞的获取结果,或者不断的轮询
- // 通常我们希望当线程执行完一些耗时的任务后,能够自动的通知我们结果,很遗憾这在原生jdk1.8之前
- // 是不支持的,但是我们可以通过第三方的库实现真正的异步回调
- Future<String> f = executor.submit(new Callable<String>() {
- @Override
- public String call() throws Exception {
- System.out.println("task started!");
- Thread.sleep(3000);
- System.out.println("task finished!");
- return "hello";
- }
- });
- //此处阻塞main线程
- System.out.println(f.get());
- System.out.println("main thread is blocked");
- }
- }
如果想获得耗时操作的结果,可以通过get方法获取,但是该方法会阻塞当前线程,我们可以在做完剩下的某些工作的时候调用get方法试图去获取结果,也可以调用非阻塞的方法isDone来确定操作是否完成,这种方式对流程的控制很混乱,但是在jdk1.8之前只提供了这种笨拙的实现方式,以至于很多高性能的框架都实现了自己的一套异步框架,比如Netty和Guava,下面分别介绍下这三种异步的实现方式(包括jdk1.8)。
1.首先是Guava中的实现方式:
- /**
- * Guava中的Future
- *
- * @author Administrator
- *
- */
- public class GuavaFuture {
- public static void main(String[] args) {
- ExecutorService executor = Executors.newFixedThreadPool(1);
- // 使用guava提供的MoreExecutors工具类包装原始的线程池
- ListeningExecutorService listeningExecutor = MoreExecutors.listeningDecorator(executor);
- //向线程池中提交一个任务后,将会返回一个可监听的Future,该Future由Guava框架提供
- ListenableFuture<String> lf = listeningExecutor.submit(new Callable<String>() {
- @Override
- public String call() throws Exception {
- System.out.println("task started!");
- //模拟耗时操作
- Thread.sleep(3000);
- System.out.println("task finished!");
- return "hello";
- }
- });
- //添加回调,回调由executor中的线程触发,但也可以指定一个新的线程
- Futures.addCallback(lf, new FutureCallback<String>() {
- //耗时任务执行失败后回调该方法
- @Override
- public void onFailure(Throwable t) {
- System.out.println("failure");
- }
- //耗时任务执行成功后回调该方法
- @Override
- public void onSuccess(String s) {
- System.out.println("success " + s);
- }
- });
- //主线程可以继续做其他的工作
- System.out.println("main thread is running");
- }
- }
Guava提供了一套完整的异步框架,核心是可监听的Future,通过注册监听器或者回调方法实现及时获取操作结果的能力。需要提一点的是,假设添加监听的时候耗时操作已经执行完了,此时回调方法会被立即执行并不会丢失。想探究其实现方式的话可以跟一下源码,底层的原理并不难。
注:Guava 是一个 Google 的基于java1.6的类库集合的扩展项目,包括 collections, caching, primitives support, concurrency libraries, common annotations, string processing, I/O, 等等. 这些高质量的 API 可以使你的JAVa代码更加优雅,更加简洁,让你工作更加轻松愉悦。
2.谈到异步编程就不得不提一下Promise,很多函数式语言比如js原生支持Promise,但是在java界也有一些promise框架,其中就有大名鼎鼎的Netty。从Future、Callback到Promise甚至线程池,Netty实现了一套完整的异步框架,并且netty代码中也大量使用了Promise,下面是Netty中的例子:
- /**
- * netty中的promise
- *
- * @author Administrator
- *
- */
- public class PromiseTest {
- @SuppressWarnings({ "unchecked", "rawtypes" })
- public static void main(String[] args) throws Throwable {
- //线程池
- EventExecutorGroup group = new DefaultEventExecutorGroup(1);
- //向线程池中提交任务,并返回Future,该Future是netty自己实现的future
- //位于io.netty.util.concurrent包下,此处运行时的类型为PromiseTask
- Future<?> f = group.submit(new Runnable() {
- @Override
- public void run() {
- System.out.println("任务正在执行");
- //模拟耗时操作,比如IO操作
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("任务执行完毕");
- }
- });
- //增加监听
- f.addListener( new FutureListener() {
- @Override
- public void operationComplete(Future arg0) throws Exception {
- System.out.println("ok!!!");
- }
- });
- System.out.println("main thread is running.");
- }
- }
3.直到jdk1.8才算真正支持了异步操作,其中借鉴了某些框架的实现思想,但又有新的功能,同时在jdk1.8中提供了lambda表达式,使得java向函数式语言又靠近了一步。借助jdk原生的CompletableFuture可以实现异步的操作,同时结合lambada表达式大大简化了代码量。代码例子如下:
- /**
- * 基于jdk1.8实现任务异步处理
- *
- * @author Administrator
- *
- */
- public class JavaPromise {
- public static void main(String[] args) throws Throwable, ExecutionException {
- // 两个线程的线程池
- ExecutorService executor = Executors.newFixedThreadPool(2);
- //jdk1.8之前的实现方式
- CompletableFuture<String> future = CompletableFuture.supplyAsync(new Supplier<String>() {
- @Override
- public String get() {
- System.out.println("task started!");
- try {
- //模拟耗时操作
- Thread.sleep(2000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- return "task finished!";
- }
- }, executor);
- //采用lambada的实现方式
- future.thenAccept(e -> System.out.println(e + " ok"));
- System.out.println("main thread is running");
- }
- }