一、背景
在我们实际开发中,面对IO密集的场景,例如进行文档的读取或者调用下游接口的时候时候,会面临一个的等待的问题,在传统的串行同步执行方法里面,比较消耗时长。为了提高性能,我们往往会采取异步编程的方法来解决接口时长问题。
在我们进行异步编程的时候,会面临两个选择,一个Feature 的API方法来进行异步编程。
Feature异步编程虽然解决主线程串行耗时问题,但也存在一些问题:
1)Feature 只能使用 get() 方法来阻塞Feature 异步线程的执行,保证异步方法执行完成之后,再结束阻塞。
2)Feature 不具备自动回调的功能。只可以使用get()阻塞等待。
3)无法解决多个任务相互依赖的问题。
4)不能将多个Feature 合在一起,例如无法将多个不同的Feature 任务,在全部运行完成之后执行某一个方法。
5)无API方法进行异常处理。
二、实例
工具类
package com.hihonor.tmt.bean.util;
/*
* Feature的工具类
* */
import java.util.ArrayList;
import java.util.List;
import java.util.StringJoiner;
public class FeatureUtil {
/*
* 读取文件
* */
public static String readFile() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
String file1 = "TMD,开始了一天的晚班学习,他要学会异步编程的方法的调用,尼玛,学不会不睡觉了";
return file1;
}
/*
* 睡眠指定时间
* */
public static void sleep(Long time) {
try {
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/*
* 读取敏感词汇
* */
public static List<String> readSensitiveFile() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
List<String> list = new ArrayList<>();
list.add("TMD");
list.add("尼玛");
return list;
}
/*
* 打印控制台
* */
public static void systemIn(String message) {
// 时间戳--线程id--线程名--日志信息
String result = new StringJoiner(" | ")
.add(String.valueOf(System.currentTimeMillis()))
.add(String.format("%2d", Thread.currentThread().getId()))
.add(Thread.currentThread().getName())
.add(message)
.toString();
System.out.println(result);
}
}
串行方法
package com.hihonor.tmt.bean.feature;
import com.hihonor.tmt.bean.util.FeatureUtil;
import java.math.BigDecimal;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
/*
* 本类使用串行
* 方法完成同步调用
* */
public class SerialRun {
public static void main(String[] args) {
Long start = System.currentTimeMillis();
// 读取文件
String fileStr = FeatureUtil.readFile();
// 读取敏感词汇
List<String> sensitiveStrs = FeatureUtil.readSensitiveFile();
// 替换敏感词汇
for (String sensitiveStr: sensitiveStrs) {
FeatureUtil.sleep(1000L);
fileStr = fileStr.replaceAll(sensitiveStr, "***");
}
FeatureUtil.systemIn(fileStr);
Long end = System.currentTimeMillis();
System.out.println("执行时长------"+ String.valueOf((end - start)/1000.0) +"秒");
}
}
执行结果:
1687789591487 | 1 | main | ***,开始了一天的晚班学习,他要学会异步编程的方法的调用,***,学不会不睡觉了
执行时长------5.05秒
Feature 方法
package com.hihonor.tmt.bean.feature;
import com.hihonor.tmt.bean.util.FeatureUtil;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/*
* 本类使用Feature 方法完成异步调用
* */
public class FeatureRun {
public static void main(String[] args) {
Long start = System.currentTimeMillis();
// 创建线程池
ExecutorService executor = Executors.newFixedThreadPool(5);
// 读取文件
Future<String> feature1 = executor.submit(FeatureUtil::readFile);
// 读取敏感词汇
Future<List<String>> feature2 = executor.submit(FeatureUtil::readSensitiveFile);
try {
// 敏感词汇替换
String finalFeature1Str = feature1.get();
List<String> finalFeature2Str2 = feature2.get();
executor.submit(() -> {
String result = finalFeature1Str;
// 替换敏感词汇
for (String finalFeature2Str : finalFeature2Str2) {
result = result.replaceAll(finalFeature2Str, "***");
}
});
} catch (Exception e) {
e.printStackTrace();
}finally {
executor.shutdown();
}
Long end = System.currentTimeMillis();
System.out.println("执行时长------" + String.valueOf((end - start) / 1000.0) + "秒");
}
}
执行结果
执行时长------2.037秒
1687789827275 | 22 | pool-1-thread-3 | ***,开始了一天的晚班学习,他要学会异步编程的方法的调用,***,学不会不睡觉了
CompletableFuture方法
package com.hihonor.tmt.bean.feature;
import com.hihonor.tmt.bean.util.FeatureUtil;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/*
* 本类用来只执行 compleature 异步方法
* */
public class ComplateFeatureRun {
public static void main(String[] args) throws Exception {
Long start = System.currentTimeMillis();
// 创建线程池
ExecutorService executor = Executors.newFixedThreadPool(5);
// supplyAsync 带返回值
// 读取文件内容
CompletableFuture<String> comFeature1 = CompletableFuture.supplyAsync(FeatureUtil::readFile, executor);
// 读取敏感词
CompletableFuture<List<String>> comFeature2 = CompletableFuture.supplyAsync(FeatureUtil::readSensitiveFile);
// runAsync 不带返回值
String join1 = comFeature1.get();
List<String> join2 = comFeature2.get();
// 替换敏感词操作
CompletableFuture<Void> feature3 = CompletableFuture.runAsync(() -> {
String result = join1;
for (String finalFeature2Str : join2) {
result = result.replaceAll(finalFeature2Str, "***");
}
FeatureUtil.systemIn(result);
});
// 等待替操作执行完成
feature3.get();
// 关闭线程池
executor.shutdown();
Long end = System.currentTimeMillis();
FeatureUtil.systemIn("执行时长------" + String.valueOf((end - start) / 1000.0) + "秒");
}
}
执行结果
1687793871266 | 21 | ForkJoinPool.commonPool-worker-9 | ***,开始了一天的晚班学习,他要学会异步编程的方法的调用,***,学不会不睡觉了
1687793871274 | 1 | main | 执行时长------2.046秒
API方法:
1)supplyAsync
----- 该方法执行完成任务之后,会有返回值返回。
2)runAsync
------ 该方法执行完成之后,无返回值。
三、回调函数
回调函数方法,thenAppply(Function<T,R>) 方法,该方法可以将任务执行完成之后,自动将结果获取,并对结果进行操作。
转换后的结果可以交给主线程获取,也可以进一步转换。
也就是说这个结果thenApply(Function<T,R>)是一个带返回值的回调方法。
package com.hihonor.tmt.bean.feature;
import com.hihonor.tmt.bean.util.FeatureUtil;
import java.util.List;
import java.util.concurrent.CompletableFuture;
/*
* 本类是用来进行completableFeature 异步方法的自动回调
* 这样方法执行完成之后可以自动调用方法,而不是需要我们 使用 get() 方法进行获取。
* thenApply ---对上一步方法的结果进行处理,并返回处理结果
* */
public class thenApplyComplateFFeature {
public static void main(String[] args) {
Long start = System.currentTimeMillis();
// 读取敏感词汇
CompletableFuture<List<String>> featureList = CompletableFuture
.supplyAsync(FeatureUtil::readSensitiveFile);
// 读取文章内容
CompletableFuture<String> feature = CompletableFuture
.supplyAsync(FeatureUtil::readFile)
// 回调方法完成敏感词调换
.thenApply(context -> {
List<String> strings = featureList.join();
//替换
return FeatureUtil.repleaceStr(context, strings);
});
FeatureUtil.systemIn(feature.join());
Long end = System.currentTimeMillis();
FeatureUtil.systemIn("执行时长------" + String.valueOf((end - start) / 1000.0) + "秒");
}
}
执行结果
1687880579318 | 1 | main | ***,开始了一天的晚班学习,他要学会异步编程的方法的调用,***,学不会不睡觉了
1687880579318 | 1 | main | 执行时长------2.036秒
如果我们再执行异步方法的回调方法之后,无需进行返回值,则可以使用 thenAeecpt(Function<T,R>) 方法,该方法只是将异步执行结果的方法进一步加工处理,处理完成之后,没有返回。
也就是一个没有返回值的回调方法。
package com.hihonor.tmt.bean.feature;
import com.hihonor.tmt.bean.util.FeatureUtil;
import java.util.List;
import java.util.concurrent.CompletableFuture;
/*
* 本类是用来进行completableFeature 异步方法的自动回调
* 这样方法执行完成之后可以自动调用方法,而不是需要我们 使用 get() 方法进行获取。
* thenAccept ---对上一步方法的结果进行处理,不返回处理结果
* */
public class thenAcceptComplateFFeature {
public static void main(String[] args) throws InterruptedException {
Long start = System.currentTimeMillis();
// 读取敏感词汇
CompletableFuture<List<String>> featureList = CompletableFuture
.supplyAsync(FeatureUtil::readSensitiveFile);
// 读取文章内容
CompletableFuture
.supplyAsync(FeatureUtil::readFile)
// 回调方法完成敏感词调换
.thenAccept(context -> {
List<String> strings = featureList.join();
//替换
String str = FeatureUtil.repleaceStr(context, strings);
FeatureUtil.systemIn(str);
}).join();
Long end = System.currentTimeMillis();
FeatureUtil.systemIn("执行时长------" + String.valueOf((end - start) / 1000.0) + "秒");
}
}
执行结果
1687880601335 | 21 | ForkJoinPool.commonPool-worker-2 | ***,开始了一天的晚班学习,他要学会异步编程的方法的调用,***,学不会不睡觉了
1687880601341 | 1 | main | 执行时长------2.042秒
如果我们不对异步执行结果进行加工处理,只是想知道该异步方法是否执行完成,则可以使用thenRun(Function<T,R>) 方法。这个方法只是一个异步完成的通知方法,告诉开发者,异步方法执行完成了。
package com.hihonor.tmt.bean.feature;
import com.hihonor.tmt.bean.util.FeatureUtil;
import java.util.List;
import java.util.concurrent.CompletableFuture;
/*
* 本类是用来进行completableFeature 异步方法的自动回调
* 这样方法执行完成之后可以自动调用方法,而不是需要我们 使用 get() 方法进行获取。
* thenRun ---对上一步方法的结果不进行处理,不返回处理结果
* */
public class thenRunComplateFFeature {
public static void main(String[] args) {
Long start = System.currentTimeMillis();
// 读取敏感词汇
CompletableFuture<List<String>> featureList = CompletableFuture
.supplyAsync(FeatureUtil::readSensitiveFile);
// 读取文章内容
CompletableFuture
.supplyAsync(FeatureUtil::readFile)
// 回调方法完成通知,无法处理上一步的返回结果
.thenRun(() -> {
FeatureUtil.systemIn("执行回调方法了.......");
}).join();
Long end = System.currentTimeMillis();
FeatureUtil.systemIn("执行时长------" + String.valueOf((end - start) / 1000.0) + "秒");
}
}
执行结果
1687880634784 | 21 | ForkJoinPool.commonPool-worker-2 | 执行回调方法了.......
1687880634790 | 1 | main | 执行时长------2.044秒
总结:
如果需要回调方法对结果进行处理,并且需要回调方法处理完成之后,返回具体处理结果。则使用thenApply(Function<T,R>)方法。
如果需要回调方法对结果进行处理,回调方法处理完成之后,不返回具体处理结果。则使用thenAeecpt(Function<T,R>) 方法。
如果不对结果进行处理,只是通知开发完成异步方法的调用了,则使用thenRun(Function<T,R>) 方法。
需要注意的是,这三个方法都支持 lamda 表达式和链式调用。
四、两个任务的组合
进行两个有关联的异步任务的编排,关联任务的意思是 第一个任务的结果要传给第二个任务进行处理。
使用thenCompose方法。
package com.hihonor.tmt.bean.feature;
import com.hihonor.tmt.bean.util.FeatureUtil;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
/*
* 进行两个异步任务的编排调用-------这两个异步任务可存在依赖关系
* 例如 任务1 执行完成的结果 正是任务2的条件
* */
public class thenComposeFeature {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Long start = System.currentTimeMillis();
// 获取得到敏感词汇
CompletableFuture<List<String>> feature2 = CompletableFuture.supplyAsync(FeatureUtil::readSensitiveFile);
// 将敏感词汇传递给下一个异步任务
CompletableFuture<String> stringCompletableFuture =
feature2.thenCompose(context -> CompletableFuture.supplyAsync(() -> {
String readFile = FeatureUtil.readFile();
String str = FeatureUtil.repleaceStr(readFile, context);
return str;
}));
stringCompletableFuture.join();
FeatureUtil.systemIn(stringCompletableFuture.get());
Long end = System.currentTimeMillis();
FeatureUtil.systemIn("执行时长------" + String.valueOf((end - start) / 1000.0) + "秒");
}
}
执行结果
1687965830385 | 1 | main | ***,开始了一天的晚班学习,他要学会异步编程的方法的调用,***,学不会不睡觉了
1687965830392 | 1 | main | 执行时长------3.046秒
进行两个没有关联的异步任务的编排。
使用thenCombine方法。
package com.hihonor.tmt.bean.feature;
import com.hihonor.tmt.bean.util.FeatureUtil;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
/*
* 本类用来编排两个 没有关系的异步任务
* */
public class thenCombineComplateFeature {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Long start = System.currentTimeMillis();
CompletableFuture<String> feature1 = CompletableFuture.supplyAsync(FeatureUtil::readFile);
CompletableFuture<List<String>> feature2 = CompletableFuture.supplyAsync(FeatureUtil::readSensitiveFile);
CompletableFuture<String> stringCompletableFuture =
feature2.thenCombine(feature1, (result2, result1) -> FeatureUtil.repleaceStr(result1, result2));
stringCompletableFuture.join();
FeatureUtil.systemIn(stringCompletableFuture.get());
Long end = System.currentTimeMillis();
FeatureUtil.systemIn("执行时长------" + String.valueOf((end - start) / 1000.0) + "秒");
}
}
执行结果
1687965479248 | 1 | main | ***,开始了一天的晚班学习,他要学会异步编程的方法的调用,***,学不会不睡觉了
1687965479254 | 1 | main | 执行时长------2.045秒
问题:
上面两个方法,thenCompose和thenCombine 这两个方法虽然实现了多个异步任务的组合,但是每次都是两个组合,如果有多个任务呢?
五、多个任务的组合
1、如果我们需要多个异步任务的组合,怎么办呢?
这时候为我们提供的方法是 allof 方法。这个方法的意思是多个异步任务全部执行完成,开始进行下一步操作。
package com.hihonor.tmt.bean.feature;
import com.hihonor.tmt.bean.util.FeatureUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/*
* 本类用来实现 多个任务的组合
* allOf 方法 表示多个任务执行全部完成之后,在调用方法。
* all Of 方法 需要保证的是多个独立运行的Feature,这些任务都是独立运行的。
* */
public class allOfFeature {
public static void main(String[] args) {
Long start = System.currentTimeMillis();
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
// 异步方法集合--多个异步方法
List<CompletableFuture<String>> features = list.stream()
.map(o -> CompletableFuture.supplyAsync(FeatureUtil::readFile))
.collect(Collectors.toList());
// 一个异步方法
CompletableFuture<String> listCompletableFuture =
CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "1";
}
);
// 一个异步方法
CompletableFuture<String> feature2 =
CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "1";
}
);
// 多个异步任务组合在一起
features.add(listCompletableFuture);
features.add(feature2);
int length = features.size();
CompletableFuture[] complateFeatureRuns = features.toArray(new CompletableFuture[length]);
CompletableFuture<Void> voidCompletableFuture = CompletableFuture.allOf(complateFeatureRuns);
voidCompletableFuture.thenApply(o -> {
FeatureUtil.systemIn("三个任务执行完成");
return "1";
}).join();
Long end = System.currentTimeMillis();
FeatureUtil.systemIn("执行时长------" + String.valueOf((end - start) / 1000.0) + "秒");
}
}
执行结果
1688203379966 | 24 | ForkJoinPool.commonPool-worker-13 | 三个任务执行完成
1688203379966 | 1 | main | 执行时长------5.042秒
如果我们的需求是多个异步任务,谁先执行完就先使用谁的执行结果呢?也就是先到先处理,后到不处理。
这个时候就用到了 anyOf 方法。
package com.hihonor.tmt.bean.feature;
/*
* 本类用来实现 多个任务的组合
* anyOf 方法 表示多个任务执行只需要有一个执行完成之后,就会调用方法执行
* anyOf 方法 需要保证的是多个独立运行的Feature,这些任务都是独立运行的。
* */
import com.hihonor.tmt.bean.util.FeatureUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
public class anyOfFeature {
public static void main(String[] args) {
Long start = System.currentTimeMillis();
List<CompletableFuture<String>> features = new ArrayList<>();
CompletableFuture<String> feature1 =
CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "1";
}
);
CompletableFuture<String> feature2 =
CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "1";
}
);
features.add(feature1);
features.add(feature2);
int length = features.size();
CompletableFuture[] complateFeatureRuns = features.toArray(new CompletableFuture[length]);
// anyOf 表示的就是 只要集合里面的多个方法有一个 执行完成了,就开始下一步的调用了,也就是先到先得。
CompletableFuture<Object> voidCompletableFuture = CompletableFuture.anyOf(complateFeatureRuns);
voidCompletableFuture.thenApply(o -> {
FeatureUtil.systemIn("三个任务执行完成");
return "1";
}).join();
Long end = System.currentTimeMillis();
FeatureUtil.systemIn("执行时长------" + String.valueOf((end - start) / 1000.0) + "秒");
}
}
执行结果
1688203468922 | 21 | ForkJoinPool.commonPool-worker-2 | 三个任务执行完成
1688203468922 | 1 | main | 执行时长------1.033秒
七、异步回调链里面的异常处理
在使用异步方法之后,我们在调用多个回调方法组成回调链的时候,如果回调链里面的一个回调方法或者多个回调方法遇到异常需要如何处理呢?
使用异常处理的方法是-----exceptionally(Exception ex) 方法
这个方法的作用是,发现异常之后,会直接捕获处理。
package com.hihonor.tmt.bean.feature;
import com.hihonor.tmt.bean.util.FeatureUtil;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
/*
* 本类用来处理 异步线程中的异常
* 本类采用的异常处理方法是 exceptionally ,这个方法的作用是,发现异常之后,会直接捕获处理。
* 异常的方法 和 exceptionally 之间的方法全部不执行,和 try catch 一致
* exceptionally 方法执行完成之后,后面的方法继续执行
*
* 但若是有异常在 exceptionally 之后,则 无法处理异常,方法会直接报错
*
* 所以 exceptionally 一般放在 回调链的尾部
* 因为只要回调链中有 exceptionally,只有最后一个会生效
* */
public class excetionallyFeature {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Long start = System.currentTimeMillis();
CompletableFuture<String> stringCompletableFuture = CompletableFuture
.supplyAsync(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int i = 1 / 0;
return "result1+";
}).exceptionally(context -> {
System.out.println("出现异常" + context.getMessage());
return "Exceptipon + ";
})
.thenApply(context -> {
return context + "result2 + ";
}).thenApply(context -> {
return context + "result3";
});
FeatureUtil.systemIn(stringCompletableFuture.get());
FeatureUtil.systemIn(stringCompletableFuture.join());
Long end = System.currentTimeMillis();
FeatureUtil.systemIn("执行时长------" + String.valueOf((end - start) / 1000.0) + "秒");
}
}
执行结果
出现异常java.lang.ArithmeticException: / by zero
1688204119305 | 1 | main | Exceptipon + result2 + result3
1688204119311 | 1 | main | Exceptipon + result2 + result3
1688204119312 | 1 | main | 执行时长------2.053秒
回调链中有 里面的 exceptionally 方法捕获异常的处理逻辑是,如果exceptionally 捕获到异常之后,前面执行的所有方法结果归0,从这个exceptionally 向下继续执行。
package com.hihonor.tmt.bean.feature;
import com.hihonor.tmt.bean.util.FeatureUtil;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
/*
* 本类用来处理 异步线程中的异常
* 本类采用的异常处理方法是 exceptionally ,这个方法的作用是,发现异常之后,会直接捕获处理。
* 异常的方法 和 exceptionally 之间的方法全部不执行,和 try catch 一致
* exceptionally 方法执行完成之后,后面的方法继续执行
*
* 但若是有异常在 exceptionally 之后,则 无法处理异常,方法会直接报错
*
* 所以 exceptionally 一般放在 回调链的尾部
* 因为只要回调链中有 exceptionally,只有最后一个会生效
* */
public class excetionallyFeature {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Long start = System.currentTimeMillis();
CompletableFuture<String> stringCompletableFuture = CompletableFuture
.supplyAsync(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int i = 1 / 0;
return "result1+";
}).exceptionally(context -> {
System.out.println("出现异常" + context.getMessage());
return "Exception 1 + ";
})
.thenApply(context -> {
return context + "result2 + ";
}).thenApply(context -> {
int i = 1 / 0;
return context + "result3";
}).exceptionally(context -> {
System.out.println("出现异常" + context.getMessage());
return "Exception 2 + ";
});
FeatureUtil.systemIn(stringCompletableFuture.get());
FeatureUtil.systemIn(stringCompletableFuture.join());
Long end = System.currentTimeMillis();
FeatureUtil.systemIn("执行时长------" + String.valueOf((end - start) / 1000.0) + "秒");
}
}
执行结果
出现异常java.lang.ArithmeticException: / by zero
1688203872445 | 1 | main | Exception 2 +
1688203872450 | 1 | main | Exception 2 +
1688203872451 | 1 | main | 执行时长------2.049秒
如果exceptionally 在回调链尾端,出现异常的回调方法 和 exceptionally 之间的方法全部不执行,和 try catch 一致
package com.hihonor.tmt.bean.feature;
import com.hihonor.tmt.bean.util.FeatureUtil;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
/*
* 本类用来处理 异步线程中的异常
* 本类采用的异常处理方法是 exceptionally ,这个方法的作用是,发现异常之后,会直接捕获处理。
* 异常的方法 和 exceptionally 之间的方法全部不执行,和 try catch 一致
* exceptionally 方法执行完成之后,后面的方法继续执行
*
* 但若是有异常在 exceptionally 之后,则 无法处理异常,方法会直接报错
*
* 所以 exceptionally 一般放在 回调链的尾部
* 因为只要回调链中有 exceptionally,只有最后一个会生效
* */
public class excetionallyFeature {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Long start = System.currentTimeMillis();
CompletableFuture<String> stringCompletableFuture = CompletableFuture
.supplyAsync(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int i = 1 / 0;
return "result1+";
}).thenApply(context -> {
return context + "result2 + ";
}).thenApply(context -> {
return context + "result3";
}).exceptionally(context -> {
System.out.println("出现异常" + context.getMessage());
return "Exceptipon + ";
});
FeatureUtil.systemIn(stringCompletableFuture.get());
FeatureUtil.systemIn(stringCompletableFuture.join());
Long end = System.currentTimeMillis();
FeatureUtil.systemIn("执行时长------" + String.valueOf((end - start) / 1000.0) + "秒");
}
}
执行结果
出现异常java.lang.ArithmeticException: / by zero
1688204252555 | 1 | main | Exceptipon +
1688204252555 | 1 | main | Exceptipon +
1688204252555 | 1 | main | 执行时长------2.035秒
package com.hihonor.tmt.bean.feature;
import com.hihonor.tmt.bean.util.FeatureUtil;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
/*
* 本类用来处理 异步线程中的异常
* 本类采用的异常处理方法是 exceptionally ,这个方法的作用是,发现异常之后,会直接捕获处理。
* 异常的方法 和 exceptionally 之间的方法全部不执行,和 try catch 一致
* exceptionally 方法执行完成之后,后面的方法继续执行
*
* 但若是有异常在 exceptionally 之后,则 无法处理异常,方法会直接报错
*
* 所以 exceptionally 一般放在 回调链的尾部
* 因为只要回调链中有 exceptionally,只有最后一个会生效
* */
public class excetionallyFeature {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Long start = System.currentTimeMillis();
CompletableFuture<String> stringCompletableFuture = CompletableFuture
.supplyAsync(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int i = 1 / 0;
return "result1+";
}).exceptionally(context -> {
System.out.println("出现异常" + context.getMessage());
return "Exceptipon1 + ";
}).thenApply(context -> {
int i = 1 / 0;
return context + "result2 + ";
}).exceptionally(context -> {
System.out.println("出现异常" + context.getMessage());
return "Exceptipon2 + ";
}).thenApply(context -> {
int i = 1 / 0;
return context + "result3";
}).thenApply(context -> {
int i = 1 / 0;
return context + "result4";
}).thenApply(context -> {
int i = 1 / 0;
return context + "result5";
}).exceptionally(context -> {
System.out.println("出现异常" + context.getMessage());
return "Exceptipon2 + ";
});
FeatureUtil.systemIn(stringCompletableFuture.get());
FeatureUtil.systemIn(stringCompletableFuture.join());
Long end = System.currentTimeMillis();
FeatureUtil.systemIn("执行时长------" + String.valueOf((end - start) / 1000.0) + "秒");
}
}
执行结果
出现异常java.lang.ArithmeticException: / by zero
1688204480429 | 1 | main | Exceptipon2 +
1688204480429 | 1 | main | Exceptipon2 +
1688204480429 | 1 | main | 执行时长------2.031秒
处理异常的方法除了 exceptionally ,还有handle 方法。这个方法和 exceptionally 不同之处在于,他得到的数据除了异常还有 上个回调方法里面的返回值,个人理解类似于 回调方法和异常方法exceptionally 的结合体。
package com.hihonor.tmt.bean.feature;
import com.hihonor.tmt.bean.util.FeatureUtil;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
/*
* 本类也用户方法的 异常处理 --- handle 方法
* handle 方法 和 exceptionally 方法不同
* handle() 方法常常用来恢复回调链中的异常,恢复之后继续向下执行
* 因为 handle() 用于异常回复,所以这个handle() 往往用于在异常之后,而不是在回调链的尾部
* */
public class handleFeature {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Long start = System.currentTimeMillis();
CompletableFuture<String> stringCompletableFuture = CompletableFuture
.supplyAsync(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//int i = 1 / 0;
return "result1+";
}).handle((context, ex) -> {
if (ex != null) {
System.out.println("出现异常" + ex.getMessage());
return "Exception1 + ";
}
return context + "handle1 + ";
})
.thenApply(context -> {
return context + "result2 + ";
}).thenApply(context -> {
return context + "result3";
});
FeatureUtil.systemIn(stringCompletableFuture.get());
FeatureUtil.systemIn(stringCompletableFuture.join());
Long end = System.currentTimeMillis();
FeatureUtil.systemIn("执行时长------" + String.valueOf((end - start) / 1000.0) + "秒");
}
}
执行结果
1688205371978 | 1 | main | result1+handle1 + result2 + result3
1688205371984 | 1 | main | result1+handle1 + result2 + result3
1688205371986 | 1 | main | 执行时长------2.045秒
package com.hihonor.tmt.bean.feature;
import com.hihonor.tmt.bean.util.FeatureUtil;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
/*
* 本类也用户方法的 异常处理 --- handle 方法
* handle 方法 和 exceptionally 方法不同
* handle() 方法常常用来恢复回调链中的异常,恢复之后继续向下执行
* 因为 handle() 用于异常回复,所以这个handle() 往往用于在异常之后,而不是在回调链的尾部
* */
public class handleFeature {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Long start = System.currentTimeMillis();
CompletableFuture<String> stringCompletableFuture = CompletableFuture
.supplyAsync(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int i = 1 / 0;
return "result1+";
}).handle((context, ex) -> {
if (ex != null) {
System.out.println("出现异常" + ex.getMessage());
return "Exception1 + ";
}
return context + "handle1 + ";
})
.thenApply(context -> {
return context + "result2 + ";
}).thenApply(context -> {
return context + "result3";
});
FeatureUtil.systemIn(stringCompletableFuture.get());
FeatureUtil.systemIn(stringCompletableFuture.join());
Long end = System.currentTimeMillis();
FeatureUtil.systemIn("执行时长------" + String.valueOf((end - start) / 1000.0) + "秒");
}
}
执行结果
出现异常java.lang.ArithmeticException: / by zero
1688205398192 | 1 | main | Exception1 + result2 + result3
1688205398192 | 1 | main | Exception1 + result2 + result3
1688205398192 | 1 | main | 执行时长------2.032秒
从上面的两个案例可以看出来,不论 是否有异常,handle 都会执行。
下面我们再看看exceptionally 方法,会发现 有异常才会执行exceptionally 方法
package com.hihonor.tmt.bean.feature;
import com.hihonor.tmt.bean.util.FeatureUtil;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
/*
* 本类用来处理 异步线程中的异常
* 本类采用的异常处理方法是 exceptionally ,这个方法的作用是,发现异常之后,会直接捕获处理。
* 异常的方法 和 exceptionally 之间的方法全部不执行,和 try catch 一致
* exceptionally 方法执行完成之后,后面的方法继续执行
*
* 但若是有异常在 exceptionally 之后,则 无法处理异常,方法会直接报错
*
* 所以 exceptionally 一般放在 回调链的尾部
* 因为只要回调链中有 exceptionally,只有最后一个会生效
* */
public class excetionallyFeature {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Long start = System.currentTimeMillis();
CompletableFuture<String> stringCompletableFuture = CompletableFuture
.supplyAsync(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// int i = 1 / 0;
return "result1+";
}).exceptionally(context -> {
System.out.println("出现异常" + context.getMessage());
return "Exceptipon + ";
})
.thenApply(context -> {
return context + "result2 + ";
}).thenApply(context -> {
return context + "result3";
});
FeatureUtil.systemIn(stringCompletableFuture.get());
FeatureUtil.systemIn(stringCompletableFuture.join());
Long end = System.currentTimeMillis();
FeatureUtil.systemIn("执行时长------" + String.valueOf((end - start) / 1000.0) + "秒");
}
}
执行结果
1688205996739 | 1 | main | result1+result2 + result3
1688205996745 | 1 | main | result1+result2 + result3
1688205996746 | 1 | main | 执行时长------2.038秒
八、两个异步任务交互的选择性处理
有一个类似于 anyOf 的方法,也是先到先处理。 这个方法就是 applyToEither 方法。
但是这个方法每次比较的是两个异步任务,anyOf 比较的是多个。这也是二者的不同之处。
package com.hihonor.tmt.bean.feature;
import com.hihonor.tmt.bean.util.FeatureUtil;
import java.util.concurrent.CompletableFuture;
/*
* 多个异步任务之间选择性处理----先到先处理
*
* */
public class applyToEitherFeature {
public static void main(String[] args) {
Long start = System.currentTimeMillis();
CompletableFuture<String> feature1 = CompletableFuture.supplyAsync(() -> {
FeatureUtil.sleep(2000l);
return "feature1";
});
CompletableFuture<String> feature2 = CompletableFuture.supplyAsync(() -> {
FeatureUtil.sleep(3000l);
return "feature2";
});
CompletableFuture<String> stringCompletableFuture = feature1.applyToEither(feature2, context -> {
return context;
});
FeatureUtil.systemIn(stringCompletableFuture.join());
Long end = System.currentTimeMillis();
FeatureUtil.systemIn("执行时长------" + String.valueOf((end - start) / 1000.0) + "秒");
}
}
执行结果
1688207003394 | 1 | main | feature1
1688207003401 | 1 | main | 执行时长------2.045秒
如果想比较多个的话,需要多次执行applyToEither 方法。
package com.hihonor.tmt.bean.feature;
import com.hihonor.tmt.bean.util.FeatureUtil;
import java.util.concurrent.CompletableFuture;
/*
* 多个异步任务之间选择性处理----先到先处理
*
* */
public class applyToEitherFeature {
public static void main(String[] args) {
Long start = System.currentTimeMillis();
CompletableFuture<String> feature1 = CompletableFuture.supplyAsync(() -> {
FeatureUtil.sleep(2000l);
return "feature1";
});
CompletableFuture<String> feature2 = CompletableFuture.supplyAsync(() -> {
FeatureUtil.sleep(3000l);
return "feature2";
});
CompletableFuture<String> feature3 = CompletableFuture.supplyAsync(() -> {
FeatureUtil.sleep(1000l);
return "feature3";
});
CompletableFuture<String> feature4 = CompletableFuture.supplyAsync(() -> {
FeatureUtil.sleep(500l);
return "feature4";
});
CompletableFuture<String> feature5 = CompletableFuture.supplyAsync(() -> {
FeatureUtil.sleep(300l);
return "feature5";
});
CompletableFuture<String> stringCompletableFuture = feature1.applyToEither(feature2, context -> {
return context;
}).applyToEither(feature3,context ->{
return context;
}).applyToEither(feature4,context ->{
return context;
}).applyToEither(feature5,context ->{
return context;
});
FeatureUtil.systemIn(stringCompletableFuture.join());
Long end = System.currentTimeMillis();
FeatureUtil.systemIn("执行时长------" + String.valueOf((end - start) / 1000.0) + "秒");
}
}
执行结果
1688207158874 | 1 | main | feature5
1688207158884 | 1 | main | 执行时长------0.344秒
这个applyToEither 方法 执行完成有返回值,如果不需要返回值的是,也可以使用 acceptEither。
这个acceptEither 方法,也是先到先得,不同的是 这个方法只对返回的结果进行消费,并不会将处理之后的结果返回。类似于 thenAccept ()方法。
package com.hihonor.tmt.bean.feature;
import com.hihonor.tmt.bean.util.FeatureUtil;
import java.util.concurrent.CompletableFuture;
/*
* 多个异步任务之间选择性处理----先到先处理
*
* */
public class acceptEitherFeature {
public static void main(String[] args) {
Long start = System.currentTimeMillis();
CompletableFuture<String> feature1 = CompletableFuture.supplyAsync(() -> {
FeatureUtil.sleep(2000l);
return "feature1";
});
CompletableFuture<String> feature2 = CompletableFuture.supplyAsync(() -> {
FeatureUtil.sleep(3000l);
return "feature2";
});
CompletableFuture<String> feature3 = CompletableFuture.supplyAsync(() -> {
FeatureUtil.sleep(1000l);
return "feature3";
});
CompletableFuture<String> feature4 = CompletableFuture.supplyAsync(() -> {
FeatureUtil.sleep(500l);
return "feature4";
});
CompletableFuture<String> feature5 = CompletableFuture.supplyAsync(() -> {
FeatureUtil.sleep(300l);
return "feature5";
});
feature1.acceptEither(feature5, context -> {
FeatureUtil.systemIn("获取得到"+context);
}).join();
Long end = System.currentTimeMillis();
FeatureUtil.systemIn("执行时长------" + String.valueOf((end - start) / 1000.0) + "秒");
}
}
执行结果
1688207552962 | 24 | ForkJoinPool.commonPool-worker-13 | 获取得到feature5
1688207552972 | 1 | main | 执行时长------0.348秒
如果希望先到先处理,不需要返回值,也不需要消费上一步的结果,只是单纯通知多个任务有一个执行完了。可以使用 runAfterEither 方法
package com.hihonor.tmt.bean.feature;
import com.hihonor.tmt.bean.util.FeatureUtil;
import java.util.concurrent.CompletableFuture;
/*
* 多个异步任务之间选择性处理----先到先处理
*
* */
public class runAfterEitherFeature {
public static void main(String[] args) {
Long start = System.currentTimeMillis();
CompletableFuture<String> feature1 = CompletableFuture.supplyAsync(() -> {
FeatureUtil.sleep(2000l);
return "feature1";
});
CompletableFuture<String> feature2 = CompletableFuture.supplyAsync(() -> {
FeatureUtil.sleep(3000l);
return "feature2";
});
CompletableFuture<String> feature3 = CompletableFuture.supplyAsync(() -> {
FeatureUtil.sleep(1000l);
return "feature3";
});
CompletableFuture<String> feature4 = CompletableFuture.supplyAsync(() -> {
FeatureUtil.sleep(500l);
return "feature4";
});
CompletableFuture<String> feature5 = CompletableFuture.supplyAsync(() -> {
FeatureUtil.sleep(300l);
return "feature5";
});
feature1.runAfterEither(feature2, ()-> {
FeatureUtil.systemIn("已经比较执行完成了1和2");
}).runAfterEither(feature5,()->{
FeatureUtil.systemIn("已经执行完成了1和2和5");
}).runAfterEither(feature4,()->{
FeatureUtil.systemIn("已经执行完成了1和2和5和4");
}).runAfterEither(feature3,()->{
FeatureUtil.systemIn("已经执行完成了1和2和5和4和3");
}).join();
Long end = System.currentTimeMillis();
FeatureUtil.systemIn("执行时长------" + String.valueOf((end - start) / 1000.0) + "秒");
}
}
执行结果
1688208179054 | 24 | ForkJoinPool.commonPool-worker-13 | 已经执行完成了1和2和5
1688208179064 | 24 | ForkJoinPool.commonPool-worker-13 | 已经执行完成了1和2和5和4
1688208179064 | 24 | ForkJoinPool.commonPool-worker-13 | 已经执行完成了1和2和5和4和3
1688208179064 | 1 | main | 执行时长------0.347秒
从上面的案例可以看出,只有有一个执行完成了,这个 runAfterEither 方法后续的 runAfterEither 方法也会执行。
回调方法 | 先到先执行的回调方法 | 含义 |
thenApply | applyToEither | 有返回值,处理上一步返回结果,处理之后的结果可以是返回值返回。 |
thenAccept | acceptEither | 无返回值,只处理上一步返回结果,处理之后的结果,无返回值。 |
thenRun | runAfterEither | 无返回值,也不处理上一步的结果,只是单纯通知 |
注意: get() 方法和join()方法都是等待异步方法执行完成之后,获取方法执行结果。不同之处在于,get()方法抛出检查时异常,需要程序必须处理;而 join() 方法 抛出的是运行时异常,程序可以不处理。所以 join() 更适合于流式编程。
使用异步线程,不可避免的会使用到线程池,这个线程池里面的线程数量如何设定呢?
两个原则:
1)IO密集的方法,也就是涉及到外部接口的调用,文件的读取这种耗时等待外部结果的方法,就是IO密集。线程数是 cpu的核数 * 2。
2)如果是cpu计算密集的方法,也就是方法里面都在本线程进行大量计算,循环,判断这些方法,是cpu计算密集的方法.线程数是 cpu的核数+1。