在系统开发过程中我们经常会遇到很多串行化比较耗时的业务逻辑。但是很多时候如果仔细分析会发现其实很多这种串行业务部分是可以拆解为并行的。这时候我们就可以使用CompletableFuture来进行异步任务编排。
举个例子:
假如现在有一个业务:
分为如下几步:
1.业务一 耗时 0.5秒
2.业务二耗时0.5秒
3.业务三耗时1秒
4.业务四耗时0.5秒(需要用到业务一的结果)
5.业务五耗时 1秒 (需要用到业务二或者业务三的结果)
假如说目前全部串行化执行那就是3.5秒,如果我们将业务逻辑重新进行异步编排,如果在理想状态下很有可能将耗时控制到1.5秒左右。而CompletableFuture就能很方便的帮助我们实现异步任务编排,而且支持链式调用,非常方便。
一、CompletableFuture开启异步任务
1.无返回值,不使用自定义线程池
package com.xingli.springlearningdemo.thread;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* description: TestDemo <br>
*
* @date: 2022/7/15 0015 上午 11:03 <br>
* @author: William <br>
* version: 1.0 <br>
*/
public class TestDemo {
public static void main(String[] args) {
System.out.println("进入主线程=============");
CompletableFuture.runAsync(()->{
System.out.println("异步执行====== ");
});
System.out.println("主线程结束=============");
}
}
执行结果如下:
2.无返回值,使用自定义线程池
package com.xingli.springlearningdemo.thread;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* description: TestDemo <br>
*
* @date: 2022/7/15 0015 上午 11:03 <br>
* @author: William <br>
* version: 1.0 <br>
*/
public class TestDemo {
/**
* 自定义线程池
*/
public static ExecutorService service = Executors.newFixedThreadPool(5);
public static void main(String[] args) {
System.out.println("进入主线程=============");
CompletableFuture.runAsync(()->{
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("异步执行====== ");
},service);
System.out.println("主线程结束=============");
}
}
执行结果如下:
3.有返回值,不用自定义线程池
package com.xingli.springlearningdemo.thread;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* description: TestDemo <br>
*
* @date: 2022/7/15 0015 上午 11:03 <br>
* @author: William <br>
* version: 1.0 <br>
*/
public class TestDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
System.out.println("进入主线程=============");
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
int l = 100 / 5;
System.out.println("异步执行====== ");
return l;
});
System.out.println("主线程结束============="+future.get());
}
}
执行结果如下:
4.有返回值,使用自定义线程池
package com.xingli.springlearningdemo.thread;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* description: TestDemo <br>
*
* @date: 2022/7/15 0015 上午 11:03 <br>
* @author: William <br>
* version: 1.0 <br>
*/
public class TestDemo {
/**
* 自定义线程池
*/
public static ExecutorService service = Executors.newFixedThreadPool(5);
public static void main(String[] args) throws ExecutionException, InterruptedException {
System.out.println("进入主线程=============");
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
int l = 100 / 5;
System.out.println("异步执行====== ");
return l;
},service);
System.out.println("主线程结束============="+future.get());
}
}
执行结果如下:
二、对当前任务返回值进行处理(基于supplyAsync有返回值情况下)
其实封装了很多对结果处理的接口,我就拿一种我用到的handleAsync来说一下。
public <U> CompletableFuture<U> handleAsync(
BiFunction<? super T, Throwable, ? extends U> fn) {
return uniHandleStage(asyncPool, fn);
}
可以看到该方法有两个参数,一种一个是上一步的结果,另一个则是上一步的异常。而且如果出现异常则第一个参数为null,如果没有异常则第二个参数为null。
package com.xingli.springlearningdemo.thread;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* description: TestDemo <br>
*
* @date: 2022/7/15 0015 上午 11:03 <br>
* @author: William <br>
* version: 1.0 <br>
*/
public class TestDemo {
/**
* 自定义线程池
*/
public static ExecutorService service = Executors.newFixedThreadPool(5);
public static void main(String[] args) throws ExecutionException, InterruptedException {
System.out.println("进入主线程=============");
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
int l = 100 / 0;
System.out.println("异步执行======:"+l);
return l;
},service).handleAsync((res,e)->{
if(res != null){
System.out.println("结果正常====");
return res * 5;
}
if(e != null){
System.out.println("结果异常**********"+e);
return 0;
}
return 0;
});
System.out.println("主线程结束============="+future.get());
}
}
执行结果:
如果将上面代码中的 int l = 100 / 5; 改为 int l = 100 /0;线程应该就会抛出 by zero 异常,接下来我们可以修改测试一下。
测试结果等同于预期。
此外我们如果我们不需要返回值,但是如果需要处理异常,那么我们可以使用exceptionally对异常进行单独处理。
package com.xingli.springlearningdemo.thread;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* description: TestDemo <br>
*
* @date: 2022/7/15 0015 上午 11:03 <br>
* @author: William <br>
* version: 1.0 <br>
*/
public class TestDemo {
/**
* 自定义线程池
*/
public static ExecutorService service = Executors.newFixedThreadPool(5);
public static void main(String[] args) throws ExecutionException, InterruptedException {
System.out.println("进入主线程=============");
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
int l = 100 / 0;
System.out.println("异步执行======:"+l);
return l;
},service).exceptionally((e)->{
System.out.println("结果异常**********"+e);
return 0;
});
System.out.println("主线程结束============="+future.get());
}
}
执行结果:
三、多任务组合
我们使用CompletableFuture主要就是为了完成异步任务的编排。所以第三步也是我们最重要的应用。常见的多个任务组合会有以下几种情况
1.业务二会用到业务一的结果
针对第一种情况,这个直接使用上一步handle直接对结果进行处理就好。相对比较简单。
2.业务三需要等到业务一和业务二都完成
定义:业务一future 业务二future2 业务一返回值f1 业务二返回值f2
我们如果不需要接收业务一和业务二的返回值,并且自己也不用返回值那么就可以使用future.runAfterBothAsync(future2,() ->{});
如果需要使用业务一和业务二的返回值,但是在业务三不需要返回值那么可以使用:future.thenAcceptBothAsync(future2,(f1,f2) ->{},service);
如果需要使用业务一和业务二的返回值,并且自己也需要有返回值则使用:future.thenCombineAsync(future2, (f1, f2) -> { return f1 + f2; }, service);
3.业务三需要等到业务一或者业务二其中之一完成
定义:业务一future 业务二future2 业务一返回值f1 业务二返回值f2
我们如果不需要接收业务一或者业务二的返回值,并且自己也不用返回值那么就可以使用future.runAfterEitherAsync(future2,()->{});
如果需要使用业务一或者业务二的返回值,但是在业务三不需要返回值那么可以使用:future.acceptEitherAsync(future2,(f)->{});
如果需要使用业务一或者业务二的返回值,并且自己也需要有返回值则使用:future.applyToEitherAsync(future2,(f)->{return 0;});
上面几种测试代码就不一一举例了,如果大家感兴趣可以自己根据下面的代码修改测试
package com.xingli.springlearningdemo.thread;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* description: CompletableFutureTest <br>
*
* @date: 2022/7/15 0015 上午 9:53 <br>
* @author: William <br>
* version: 1.0 <br>
*/
public class CompletableFutureTest {
public static ExecutorService service = Executors.newFixedThreadPool(5);
public static void main(String[] args) throws ExecutionException, InterruptedException {
System.out.println("进入主线程============= ");
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
int k = 10 / 2;
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("计算结果k = " + k);
return k;
},service);
CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
int k = 10 * 2;
System.out.println("计算结果k2 = " + k);
return k;
},service);
CompletableFuture<Integer> combineAsync = future.thenCombineAsync(future2, (f1, f2) -> {
System.out.println("最终执行任务3:" + f1 + "===========" + f2);
return f1 + f2;
}, service);
Integer integer = combineAsync.get();
System.out.println("integer = " + integer);
System.out.println("主线程结束============= ");
}
}
补充:此外还有allOf 和anyOff ,这两个方法可以传入多个任务。同理,allOf是等待所有任务都完成了,anyOf是有任意一个任务完成即可。
好了。到这里异步任务编排大体所用的方法都学习的差不多了,回到我们最初的业务:
业务逻辑分为如下几步:
1.业务一 耗时 0.5秒
2.业务二耗时0.5秒
3.业务三耗时1秒
4.业务四耗时0.5秒(需要用到业务一的结果)
5.业务五耗时 1秒 (需要用到业务二或者业务三的结果)
现在再看我们的业务逻辑是不是很简单啦。实现代码比较简单我就不再写了。如果有什么问题大家可以加我微信联系。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/96974.html