异步编程

所谓异步其实就是实现一个无需等待被调用函数的返回值而让操作继续运行的方法

创建任务并执行任务

无参创建

 CompletableFuture<String> noArgsFuture = new CompletableFuture<>();

传入相应任务,无返回值

runAsync方法可以在后台执行异步计算,但是此时并没有返回值。持有一个Runnable对象。

CompletableFuture noReturn = CompletableFuture.runAsync(()->{
//执行逻辑,无返回值
});

传入相应任务,有返回值

此时我们看到返回的是CompletableFuture<T>此处的T就是你想要的返回值的类型。其中的Supplier<T>是一个简单的函数式接口。

CompletableFuture<String> hasReturn = CompletableFuture.supplyAsync(new Supplier<String>() {
@Override
public String get() {
return "hasReturn";
}
});

此时可以使用lambda表达式使上面的逻辑更加清晰

CompletableFuture<String> hasReturnLambda = CompletableFuture.supplyAsync(TestFuture::get);

private static String get() {
return "hasReturnLambda";
}

获取返回值

异步任务也是有返回值的,当我们想要用到异步任务的返回值时,我们可以调用CompletableFutureget()阻塞,直到有异步任务执行完有返回值才往下执行。

我们将上面的get()方法改造一下,使其停顿十秒时间。

然后进行调用

public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<String> funtureHasReturnLambda = (CompletableFuture<String>) getFuntureHasReturnLambda();
System.out.println("Main Method Is Invoking");
funtureHasReturnLambda.get();
System.out.println("Main Method End");
}

可以看到输出如下,只有调用get()方法的时候才会阻塞当前线程。

Main Method Is Invoking
Begin Invoke getFuntureHasReturnLambda
End Invoke getFuntureHasReturnLambda
Main Method End

自定义返回值

除了等待异步任务返回值以外,我们也可以在任意时候调用complete()方法来自定义返回值。


CompletableFuture<String> funtureHasReturnLambda = (CompletableFuture<String>) getFuntureHasReturnLambda();
System.out.println("Main Method Is Invoking");
new Thread(()->{
System.out.println("Thread Is Invoking ");
try {
Thread.sleep(1000);
funtureHasReturnLambda.complete("custome value");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread End ");
}).run();
String value = funtureHasReturnLambda.get();
System.out.println("Main Method End value is "+ value);

我们可以发现输出是新起线程的输出值,当然这是因为我们的异步方法设置了等待10秒,如果此时异步方法等待1秒,新起的线程等待10秒,那么输出的值就是异步方法中的值了。

Main Method Is Invoking
Begin Invoke getFuntureHasReturnLambda
Thread Is Invoking
Thread End
Main Method End value is custome value

按顺序执行异步任务

如果有一个异步任务的完成需要依赖前一个异步任务的完成,那么该如何写呢?是调用get()方法获得返回值以后然后再执行吗?这样写有些麻烦,CompletableFuture 为我们提供了方法来完成我们想要顺序执行一些异步任务的需求。thenApply thenAccept thenRun 这三个方法。这三个方法的区别就是。

方法名 是否可获得前一个任务的返回值 是否有返回值
thenApply 能获得
thenAccept 能获得
thenRun 不可获得

所以一般来说thenAccept thenRun 这两个方法在调用链的最末端使用。接下来我们用真实的例子感受一下。


返回的信息如下

seqFutureOne seqFutureTwo
seqFutureOnethenAccept
-------------
null
-------------
thenRun
null

thenApply和thenApplyAsync的区别

我们可以发现这三个方法都带有一个后缀为Async的方法,例如thenApplyAsync。那么带Async的方法和不带此后缀的方法有什么不同呢?我们就以thenApplythenApplyAsync 两个方法进行对比,其他的和这个一样的。

这两个方法区别就在于谁去执行这个任务,如果使用thenApplyAsync ,那么执行的线程是从ForkJoinPool.commonPool()中获取不同的线程进行执行,如果使用thenApply ,如果supplyAsync方法执行速度特别快,那么thenApply 任务就是主线程进行执行,如果执行特别慢的话就是和supplyAsync 执行线程一样。接下来我们通过例子来看一下,使用sleep方法来反应supplyAsync执行速度的快慢。

//thenApply和thenApplyAsync的区别
System.out.println("-------------");
CompletableFuture<String> supplyAsyncWithSleep = CompletableFuture.supplyAsync(()->{
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "supplyAsyncWithSleep Thread Id : " + Thread.currentThread();
});
CompletableFuture<String> thenApply = supplyAsyncWithSleep
.thenApply(name -> name + "------thenApply Thread Id : " + Thread.currentThread());
CompletableFuture<String> thenApplyAsync = supplyAsyncWithSleep
.thenApplyAsync(name -> name + "------thenApplyAsync Thread Id : " + Thread.currentThread());
System.out.println("Main Thread Id: "+ Thread.currentThread());
System.out.println(thenApply.get());
System.out.println(thenApplyAsync.get());
System.out.println("-------------No Sleep");
CompletableFuture<String> supplyAsyncNoSleep = CompletableFuture.supplyAsync(()->{
return "supplyAsyncNoSleep Thread Id : " + Thread.currentThread();
});
CompletableFuture<String> thenApplyNoSleep = supplyAsyncNoSleep
.thenApply(name -> name + "------thenApply Thread Id : " + Thread.currentThread());
CompletableFuture<String> thenApplyAsyncNoSleep = supplyAsyncNoSleep
.thenApplyAsync(name -> name + "------thenApplyAsync Thread Id : " + Thread.currentThread());
System.out.println("Main Thread Id: "+ Thread.currentThread());
System.out.println(thenApplyNoSleep.get());
System.out.println(thenApplyAsyncNoSleep.get());

我们可以看到输出为

-------------
Main Thread Id: Thread[main,5,main]
supplyAsyncWithSleep Thread Id : Thread[ForkJoinPool.commonPool-worker-1,5,main]------thenApply Thread Id : Thread[ForkJoinPool.commonPool-worker-1,5,main]
supplyAsyncWithSleep Thread Id : Thread[ForkJoinPool.commonPool-worker-1,5,main]------thenApplyAsync Thread Id : Thread[ForkJoinPool.commonPool-worker-1,5,main]
-------------No Sleep
Main Thread Id: Thread[main,5,main]
supplyAsyncNoSleep Thread Id : Thread[ForkJoinPool.commonPool-worker-2,5,main]------thenApply Thread Id : Thread[main,5,main]
supplyAsyncNoSleep Thread Id : Thread[ForkJoinPool.commonPool-worker-2,5,main]------thenApplyAsync Thread Id : Thread[ForkJoinPool.commonPool-worker-2,5,main]

可以看到supplyAsync方法执行速度慢的话thenApply方法执行线程和supplyAsync 执行线程相同,如果supplyAsync 方法执行速度快的话,那么thenApply方法执行线程和Main方法执行线程相同。

组合CompletableFuture

将两个CompletableFuture组合到一起有两个方法

  1. thenCompose():当第一个任务完成时才会执行第二个操作
  2. thenCombine():两个异步任务全部完成时才会执行某些操作

thenCompose() 用法

我们定义两个异步任务,假设第二个定时任务需要用到第一个定时任务的返回值。

public static CompletableFuture<String> getTastOne(){
return CompletableFuture.supplyAsync(()-> "topOne");
} public static CompletableFuture<String> getTastTwo(String s){
return CompletableFuture.supplyAsync(()-> s + " topTwo");
}

我们利用thenCompose()方法进行编写

CompletableFuture<String> thenComposeComplet = getTastOne().thenCompose(s -> getTastTwo(s));
System.out.println(thenComposeComplet.get());

输出就是

topOne  topTwo

如果还记得前面的thenApply()方法的话,应该会想这个利用thenApply()方法也是能够实现类似的功能的。

//thenApply
CompletableFuture<CompletableFuture<String>> thenApply = getTastOne()
.thenApply(s -> getTastTwo(s));
System.out.println(thenApply.get().get());

但是我们发现返回值是嵌套返回的一个类型,而想要获得最终的返回值需要调用两次get()

thenCombine() 用法

例如我们此时需要计算两个异步方法返回值的和。求和这个操作是必须是两个异步方法得出来值的情况下才能进行计算,因此我们可以用thenCombine()方法进行计算。

CompletableFuture<Integer> thenComposeOne = CompletableFuture.supplyAsync(() -> 192);
CompletableFuture<Integer> thenComposeTwo = CompletableFuture.supplyAsync(() -> 196);
CompletableFuture<Integer> thenComposeCount = thenComposeOne
.thenCombine(thenComposeTwo, (s, y) -> s + y);
System.out.println(thenComposeCount.get());

此时thenComposeOne thenComposeTwo 都完成时才会调用传给thenCombine 方法的回调函数。

组合多个CompletableFuture

在上面我们用thenCompose()thenCombine()两个方法将两个CompletableFuture 组装起来,如果我们想要将任意数量的CompletableFuture 组合起来呢?可以使用下面两个方法进行组合。

  • allOf():等待所有CompletableFuture 完后以后才会运行回调函数
  • anyOf():只要其中一个CompletableFuture 完成,那么就会执行回调函数。注意此时其他的任务也就不执行了。

接下来演示一下两个方法的用法

//allOf()
CompletableFuture<Integer> one = CompletableFuture.supplyAsync(() -> 1);
CompletableFuture<Integer> two = CompletableFuture.supplyAsync(() -> 2);
CompletableFuture<Integer> three = CompletableFuture.supplyAsync(() -> 3);
CompletableFuture<Integer> four = CompletableFuture.supplyAsync(() -> 4);
CompletableFuture<Integer> five = CompletableFuture.supplyAsync(() -> 5);
CompletableFuture<Integer> six = CompletableFuture.supplyAsync(() -> 6); CompletableFuture<Void> voidCompletableFuture = CompletableFuture.allOf(one, two, three, four, five, six);
voidCompletableFuture.thenApply(v->{
return Stream.of(one,two,three,four, five, six)
.map(CompletableFuture::join)
.collect(Collectors.toList());
}).thenAccept(System.out::println); CompletableFuture<Void> voidCompletableFuture1 = CompletableFuture.runAsync(() -> {
try {
Thread.sleep(1000);
} catch (Exception e) { }
System.out.println("1");
});

我们定义了6个CompletableFuture 等待所有的CompletableFuture等待所有任务完成以后然后将其值输出。

anyOf()的用法

CompletableFuture<Void> voidCompletableFuture1 = CompletableFuture.runAsync(() -> {
try {
Thread.sleep(1000);
} catch (Exception e) { }
System.out.println("voidCompletableFuture1");
}); CompletableFuture<Void> voidCompletableFutur2 = CompletableFuture.runAsync(() -> {
try {
Thread.sleep(2000);
} catch (Exception e) { }
System.out.println("voidCompletableFutur2");
}); CompletableFuture<Void> voidCompletableFuture3 = CompletableFuture.runAsync(() -> {
try {
Thread.sleep(3000);
} catch (Exception e) { }
System.out.println("voidCompletableFuture3");
}); CompletableFuture<Object> objectCompletableFuture = CompletableFuture
.anyOf(voidCompletableFuture1, voidCompletableFutur2, voidCompletableFuture3);
objectCompletableFuture.get();

这里我们定义了3个CompletableFuture进行一些耗时的任务,此时第一个CompletableFuture会率先完成。打印结果如下。

voidCompletableFuture1

异常处理

我们了解了CompletableFuture 如何异步执行,如何组合不同的CompletableFuture ,如何顺序执行CompletableFuture 。那么接下来还有一个重要的一步,就是在执行异步任务时发生异常的话该怎么办。我们先写个例子。


CompletableFuture.supplyAsync(()->{
//发生异常
int i = 10/0;
return "Success";
}).thenRun(()-> System.out.println("thenRun"))
.thenAccept(v -> System.out.println("thenAccept")); CompletableFuture.runAsync(()-> System.out.println("CompletableFuture.runAsync"));

执行结果为,我们发现只要执行链中有一个发生了异常,那么接下来的链条也就不执行了,但是主流程下的其他CompletableFuture还是会运行的。

CompletableFuture.runAsync

exceptionally()

我们可以使用exceptionally进行异常的处理

//处理异常

CompletableFuture<String> exceptionally = CompletableFuture.supplyAsync(() -> {
//发生异常
int i = 10 / 0;
return "Success";
}).exceptionally(e -> {
System.out.println(e);
return "Exception has Handl";
});
System.out.println(exceptionally.get());

打印如下,可以发现其接收值是异常信息,也能够返回自定义返回值。

java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
Exception has Handl

handle()

调用handle()方法也能够捕捉到异常并且自定义返回值,他和exceptionally()方法不同一点是handle()方法无论发没发生异常都会被调用。例子如下

System.out.println("-------有异常-------");
CompletableFuture.supplyAsync(()->{
//发生异常
int i = 10/0;
return "Success";
}).handle((response,e)->{
System.out.println("Exception:" + e);
System.out.println("Response:" + response);
return response;
}); System.out.println("-------无异常-------");
CompletableFuture.supplyAsync(()->{
return "Sucess";
}).handle((response,e)->{
System.out.println("Exception:" + e);
System.out.println("Response:" + response);
return response;
});

打印如下,我们可以看到在没有发生异常的时候handle()方法也被调用了

-------有异常-------
Exception:java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
Response:null
-------无异常-------
Exception:null
Response:Sucess

源代码地址

参考文章

Java8学习之异步编程的更多相关文章

  1. NodeJS学习之异步编程

    NodeJS -- 异步编程 NodeJS最大的卖点--事件机制和异步IO,对开发者并不透明 代码设计模式 异步编程有很多特有的代码设计模式,为了实现同样的功能,使用同步方式和异步方式编写代码会有很大 ...

  2. nodejs学习笔记 —— 异步编程解决方案

    在js或者node编程中,由于异步的频繁和广度使用,使得回调和嵌套的深度导致编程的体验遇到一些挑战,如果写出优雅和好看的代码,本文主要针对异步编程的主流方案做一些总结 1.事件发布/订阅模式 事件监听 ...

  3. 《C#并发编程经典实例》学习笔记—异步编程关键字 Async和Await

    C# 5.0 推出async和await,最早是.NET Framework 4.5引入,可以在Visual Studio 2012使用.在此之前的异步编程实现难度较高,async使异步编程的实现变得 ...

  4. 09-Node.js学习笔记-异步编程

    同步API,异步API 同步API:只有当前API执行完成后,才能继续执行下一个API console.log('before'); console.log('after'); 异步API:当前API ...

  5. 学习Promise异步编程

    JavaScript引擎建立在单线程事件循环的概念上.单线程( Single-threaded )意味着同一时刻只能执行一段代码.所以引擎无须留意那些"可能"运行的代码.代码会被放 ...

  6. 借助Q.js学习javascript异步编程。

    金字塔式 //add1 function step1(n, callback) { setTimeout(function(){ callback.call(null, n + 1); }, 100) ...

  7. Java8函数之旅 (八) - 组合式异步编程

    前言 随着多核处理器的出现,如何轻松高效的进行异步编程变得愈发重要,我们看看在java8之前,使用java语言完成异步编程有哪些方案. JAVA8之前的异步编程 继承Thead类,重写run方法 实现 ...

  8. JavaScript异步编程

    前言 如果你有志于成为一个优秀的前端工程师,或是想要深入学习JavaScript,异步编程是必不可少的一个知识点,这也是区分初级,中级或高级前端的依据之一.如果你对异步编程没有太清晰的概念,那么我建议 ...

  9. 深入理解 Python 异步编程(上)

    http://python.jobbole.com/88291/ 前言 很多朋友对异步编程都处于"听说很强大"的认知状态.鲜有在生产项目中使用它.而使用它的同学,则大多数都停留在知 ...

随机推荐

  1. Python基础知识笔记-作用域

    Python 中,程序的变量并不是在哪个位置都可以访问的,访问权限决定于这个变量是在哪里赋值的. 变量的作用域决定了在哪一部分程序可以访问哪个特定的变量名称.Python的作用域一共有4种,分别是: ...

  2. 前端性能----TCP协议

    TCP属于OSI七层模型中的传输层协议,位于网络边缘,提供端到端的可靠数据传输,其有着承上启下的作用,协议数据单元为报文段(Message Segment). TCP需要提供以下功能: 分组和复用 应 ...

  3. C#中集合ArrayList与Hashtable的使用

    C#中集合ArrayList与Hashtable的使用 http://blog.csdn.net/linukey/article/details/42506819 ArrayList: 一. 注意事项 ...

  4. 使用apache 的FileUtils处理文件的复制等操作

    今日思语:春风很柔,夏风很烈,秋风清爽,东风凛冽,愿你就是春夏秋冬的风~ 平时对一些文件进行操作,比如说写文件,读文件,复制一个文件等,使用原生File操作需要读取源文件,生成流对象,再写入一个新的文 ...

  5. LeetCode 490. The Maze

    原题链接在这里:https://leetcode.com/problems/the-maze/ 题目: There is a ball in a maze with empty spaces and ...

  6. Json提取器(Json Extractor)

    Variable names:保存的变量名,后面使用${Variable names}引用 JSON Path  expressions:调试通过的json path表达式 Match Numbers ...

  7. 大数定律(Law of Large Numbers)

    大数定律:每次从总体中随机抽取1个样本,这样抽取很多次后,样本的均值会趋近于总体的期望.也可以理解为:从总体中抽取容量为n的样本,样本容量n越大,样本的均值越趋近于总体的期望.当样本容量极大时,样本均 ...

  8. 洛谷 P4149 [IOI2011]Race-树分治(点分治,不容斥版)+读入挂-树上求一条路径,权值和等于 K,且边的数量最小

    P4149 [IOI2011]Race 题目描述 给一棵树,每条边有权.求一条简单路径,权值和等于 KK,且边的数量最小. 输入格式 第一行包含两个整数 n, Kn,K. 接下来 n - 1n−1 行 ...

  9. virsh使用总结

    做下面操作前先安装这些工具: yum  install  virt-install  libvirt-admin  libvirt-client  libvirt-daemon libvirt主要的配 ...

  10. cocos: 链接错误: _lz_adler32 in liblibquickmac.a

    错误: Undefined symbols for architecture x86_64: "_adler32", referenced from: _lz_adler32 in ...