异步编排API
Amdahl定律
Amdahl加速定律的基本出发点是:1.对于很多科学计算,实时性要求很高,即在此类应用中时间是个关键因素,而计算负载是固定不变的。为此在一定的计算负载下,为达到实时性可利用增加处理器数来提高计算速度;2.因为固定的计算负载是可分布在多个处理器上的,这样增加了处理器就加快了执行速度,从而达到了加速的目的。在此意义下,1967年Amdahl推导出了固定负载的加速公式。 [2]
阿姆达尔定律是计算机系统设计的重要定量原理之一,于1967年由IBM360系列机的主要设计者阿姆达尔首先提出。该定律是指:系统中对某一部件采用更快执行方式所能获得的系统性能改进程度,取决于这种执行方式被使用的频率,或所占总执行时间的比例。阿姆达尔定律实际上定义了采取增强(加速)某部分功能处理的措施后可获得的性能改进或执行时间的加速比。简单来说是通过更快的处理器来获得加速是由慢的系统组件所限制。
阿姆达尔曾致力于并行处理系统的研究。对于固定负载情况下描述并行处理效果的加速比s,阿姆达尔经过深入研究给出了如下公式:
S=1/(1-a+a/n)
其中,a为并行计算部分所占比例,n为并行处理结点个数。这样,当1-a=0时,(即没有串行,只有并行)最大加速比s=n;当a=0时(即只有串行,没有并行),最小加速比s=1;当n→∞时,极限加速比s→ 1/(1-a),这也就是加速比的上限。例如,若串行代码占整个代码的25%,则并行处理的总体性能不可能超过4。这一公式已被学术界所接受,并被称做“阿姆达尔定律”,也称为“安达尔定理”(Amdahl law)。
简言之:程序处理速度由程序中串行的部分决定,所以减少程序串行部分是提升程序运行速度的关键。
一些减少程序串行部分的方案:
-
减少锁的竞争:
(1)减少锁的持有时间
(2)降低锁的请求频率
(3)使用带有协调机制的独占锁,这种机制允许更高的并发性 -
减小锁的范围(“快进快出”)
例如使用ConcurrentHashMap等JUC包下的同步容器来减小锁的范围。 -
减小锁的粒度
分解锁和分段锁是减小锁粒度的两种方案:
同样ConcurrentHashMap实现了使用一个包含默认16个锁的数组,大约把对于锁的请求较少
到原来的16分之1。 -
替代独占锁
(1)例如ReadWriteLock
(2)原子变量Atomic CAS无锁算法
具体业务场景
考虑以下场景:
1、获取商品的基本信息 1.5s
2、获取商品的图片信息 0.5s
3、获取商品的价格信息 1s
4、获取商品的销售属性 1s
5、获取商品详情页信息 1s
假如在同一个接口中按照以上顺序来获取商品的相关信息,至少需要5s才能完成,
对于用户来说难以忍受,如果有多个线程来处理,最多只需要1.5s以后既可完成。
使用completableFuture异步编排
JDK5新增了Future接口,用于描述一个异步计算的结果。虽然 Future 以及相关使用方法提供了异步执行任务的能力,但是对于结果的获取却是很不方便,只能通过阻塞或者轮询的方式得到任务的结果。阻塞的方式显然和我们的异步编程的初衷相违背,轮询的方式又会耗费无谓的 CPU 资源,而且也不能及时地得到计算结果。
Future接口可以构建异步应用,但依然有其局限性。它很难直接表述多个Future 结果之间的依赖性。实际开发中,我们经常需要达成以下目的:
将多个异步计算的结果合并成一个
等待Future集合中的所有任务都完成
Future完成事件(即,任务完成以后触发执行动作)
- 在Java8中,CompletableFuture提供了非常强大的Future的扩展功能,可以帮助我们简化异步编程的复杂性,并且提供了函数式编程的能力,可以通过回调的方式处理计算结果,也提供了转换和组合 CompletableFuture 的方法。
- 它可能代表一个明确完成的Future,也有可能代表一个完成阶段( CompletionStage ),
支持在计算完成以后出发一些函数或者执行一些动作。 - 它实现了Future和CompletionStage接口
API方法一览
记忆方式
-
accept 能感知上一步的执行结果
run 不感知上一步执行结果
apply 不仅可以感知上一步执行结果,还可以有返回值
async是否和上一步共用一个线程,也可以传入自己的线程池对象 -
thenCombine:组合两个future,获取两个任务的返回结果,并返回当前任务的返回值
thenAcceptBoth:组合两个future,获取两个任务的返回结果,没有返回值
runAfterBoth:只需要等待两个任务完成后,执行新的任务 -
either 两个任务中完成一个既可
-
anyOf 等待完成一个既可
allOf 等待全部完成
CompletableFuture 异步API应用小demo
/**
* @Author : suitianshuang
* @Date : 2021/9/5 16:51
*/
@Service
@Slf4j
public class ZhangSan {
public static final ExecutorService executor = Executors.newFixedThreadPool(10);
private ZhangSan() {
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
long startTime = System.currentTimeMillis();
log.info("\n开始时间...{}\n", startTime);
int i = 0;
ZhangSan zhangSan = new ZhangSan();
while (true) {
i++;
if (i > 1) {
break;
}
// 张三的一天
zhangSan.theDayOfZhangSan(i);
}
log.info("\n耗时:{}\n", System.currentTimeMillis() - startTime);
}
// 张三的钱包余额
private int balance = 0;
private synchronized void addBalance(int money) {
balance = balance + money;
}
/**
* 异步编排加速张三的一天
* <p>
* 张三早上起床刷牙、吃早饭、与此同时张三打开收音机开始听新闻,做完这些之后张三去找爸妈要钱,根据要到的钱的数目决定去做什么...
* <p>
* 张三听收音机的同时做了起床刷牙吃早饭,花费4000毫秒,于此同时收听了新闻,张三最后等待妈妈给钱花费了1000毫秒 总耗时 5000毫秒
*/
public void theDayOfZhangSan(int i) throws ExecutionException, InterruptedException {
log.info("第{}天开始了", i);
// 串行 演示张三 起床、刷牙、吃走饭,
CompletableFuture<Void> futureStep1 = CompletableFuture.runAsync(() -> {
log.info("张三醒了看了一眼微信余额,还有{}元", balance);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("张三穿好衣服起床了");
}, executor).thenRunAsync(() -> {
log.info("张三开始刷牙");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("张三刷完牙");
}, executor).thenRunAsync(() -> {
log.info("张三开始吃早饭");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("张三吃完早饭");
});
// 听新闻、和张三起床刷牙吃早饭并行
CompletableFuture<Void> futureStep02 = CompletableFuture.runAsync(() -> {
log.info("张三打开收音机,开始听新闻...");
ArrayList<String> newsList = new ArrayList<>();
newsList.add("96金60银51铜!中国代表团连续五届残奥会金牌、奖牌双第一...");
newsList.add("北海一拖船翻沉1人落水,附近油船工作人员“见死不救”被罚");
newsList.add("【国足0-3不敌澳大利亚,无缘12强赛开门红】在北京时间今天凌晨举行的2022国际足联卡塔尔世界杯预选赛亚洲区最终阶段B组 #国足vs澳大利亚# 的比赛中,上半场第24分钟、第26分钟澳大利亚球员马比尔、博伊尔先后破门,国足0-2落后;下半场第70分钟杜克补射破门再丢一球。最终国足0-3不敌澳大利亚,无缘12强赛开门红。值得一提的是,#国足全场0射正# ,而本轮过后,中国队小组排名");
try {
for (String item : newsList) {
Thread.sleep(1000);
log.info("新闻滚动播放中,这在播放:{}...", item);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("新闻结束了");
}, executor);
// 模拟 张三 演示张三 起床、刷牙、吃走饭之后 和妈妈要钱
CompletableFuture<Integer> futureGetMoneyByMom = futureStep1.thenCombineAsync(futureStep02, (f1, f2) -> {
log.info("找妈妈要100块钱...");
try {
Thread.sleep((int) (Math.random() * 1000) + 1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("妈妈转账100块钱");
return 100;
}, executor).whenCompleteAsync((res, throwable) -> {
if (throwable == null) {
addBalance(res);
} else {
log.error("妈妈没有给钱", throwable);
}
}, executor).handle((a,b)->{
b.printStackTrace();
return 0;
});
// 模拟 张三 演示张三 起床、刷牙、吃走饭之后 同时和爸爸要钱
CompletableFuture<Integer> futureGetMoneyByDad = futureStep1.thenCombineAsync(futureStep02, (f1, f2) -> {
log.info("找爸爸要10块钱...");
try {
Thread.sleep((int) (Math.random() * 1000) + 1);
} catch (InterruptedException e) {
e.printStackTrace();
}
int money = (int) (Math.random() * 100) + 1;
log.info("爸爸转账{}块钱", money);
return money;
}, executor).whenCompleteAsync((res, throwable) -> {
if (throwable == null) {
addBalance(res);
} else {
log.error("爸爸没有给钱", throwable);
}
}).exceptionally(e->{
e.printStackTrace();
return 0;
});
// 模拟无论爸爸给钱还是妈妈给钱,张三立刻行动
CompletableFuture<String> waitMoneyFuture = futureGetMoneyByMom.applyToEitherAsync(futureGetMoneyByDad, res -> {
log.info("张三看了一眼微信余额,已经有{}元", balance);
if (balance < 99) {
log.info("准备去上网");
return "上网";
}
if (balance >= 100 && balance < 200) {
log.info("准备去蹦迪");
return "蹦迪";
}
if (balance >= 200 && balance < 300) {
log.info("去约会");
return "去约会";
}
if (balance >= 1000 && balance < 10000) {
log.info("去投资");
return "去投资";
}
if (balance > 10000) {
log.info("张三财富自由了");
return "财富自由了";
}
return "继续睡觉";
}, executor);
String doWhat = waitMoneyFuture.get();
log.info("张三第{}天,去{}了", i, doWhat);
}
}