CompletableFuture asynchronous orchestration
1. CompletableFuture asynchronous orchestration
1.1 Why do you need asynchronous orchestration
Problem: The logic of querying the product details page is very complicated, and the acquisition of data requires remote calls, which will inevitably take more time.
At present, the product details page in my business contains the following 7 methods:
Get the basic details and picture list of sku
Get real-time prices
Get three levels of classification
Get sales attribute and selected state
Get product switching data
Get Poster Information
Get platform information
The above query process is OpenFeign
implemented with service calls. Assuming that each remote call takes 1s, it will take 7s to complete the execution, which is unacceptable to users.
So if there are multiple threads performing these 7 steps at the same time, will the time be shorter?
1.2 Introduction to CompletableFuture
Future
is Java 5
an added class used to describe the result of an asynchronous computation. You can use isDone
the method to check whether the calculation is complete, or use the get to block the calling thread until the calculation is completed and return the result, you can also use the cancel
method to stop the execution of the task.
In Java 8, a new class with about 50 methods has been added: CompletableFuture
, which provides a very powerful Future
extension function, which can help us simplify the complexity of asynchronous programming, and provides the ability of functional programming, which can be used in the way of callback Process calculation results, and provide conversion and combination CompletableFuture
methods.
CompletableFuture
The class implements Future
the interface, so you can still get
get the result by method blocking or polling as before, but this method is not recommended.
CompletableFuture
Both the implementation class and the implementation class FutureTask
belonging to the interface can obtain the execution result of the thread.Future
1.3 Create an asynchronous object
CompletableFuture
Four static methods are provided to create an asynchronous operation.
A method that does not specify an Executor will use ForkJoinPool.commonPool()
as its thread pool to execute asynchronous code.
-
runAsync
Method does not support return value. -
supplyAsync
Return values can be supported.
whenComplete
It can handle normal or abnormal calculation results and exceptionally
handle abnormal situations. BiConsumer<? super T,? super Throwable>
business can be defined
whenComplete
whenCompleteAsync
The difference between and :
whenComplete
: It is the thread that executes the current task to execute the task that continues to be whenComplete
executed .
whenCompleteAsync
: It is to execute and continue to submit whenCompleteAsync
this task to the thread pool for execution.
The method does not Async
end with the Action
same thread, but Async
may be executed by other threads (if the same thread pool is used, it may also be selected by the same thread for execution)
Code demo:
public class CompletableFutureDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建一个没有返回值的异步对象
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
System.out.println("没有返回值结果");
});
System.out.println(future.get());
//创建一个有返回值的异步对象
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(new Supplier<Integer>() {
@Override
public Integer get() {
int a=1/0;
return 404;
}
}).whenComplete(new BiConsumer<Integer, Throwable>() {
/**
*whenComplete 和异步对象使用用一个线程
* @param integer 异步对象执行后的返回值结果
* @param throwable 异常对象
*/
@Override
public void accept(Integer integer, Throwable throwable) {
System.out.println("whenComplete:"+integer);
System.out.println("whenComplete:"+throwable);
}
}).exceptionally(new Function<Throwable, Integer>() {
/**
* 只处理异常的回调
* @param throwable
* @return
*/
@Override
public Integer apply(Throwable throwable) {
return null;
}
}).whenCompleteAsync(new BiConsumer<Integer, Throwable>() {
/**
* whenCompleteAsync跟异步对象有可能不适用同一个线程,由线程池重新分配
* @param integer
* @param throwable
*/
@Override
public void accept(Integer integer, Throwable throwable) {
}
});
}
}
1.4 Thread serialization and parallelization methods
thenApply
Method: When a thread depends on another thread, get the result returned by the previous task and return the return value of the current task.
thenAccept
Method: Consume the processing result. Receive the processing result of the task, and consume the processing, and return no result.
thenRun
Method: As long as the above task is completed, it will start to execute thenRun
, but after the task is processed, the follow-up operation thenRun
will
Async
Execution is asynchronous by default. The so-called asynchronous here refers to not executing in the current thread.
Function<? super T,? extends U>
T:上一个任务返回结果的类型
U:当前任务的返回值类型
Code demo:
public class CompletableFutureDemo {
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor =
new ThreadPoolExecutor(
50,
500,
30,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10000)
);
//创建一个异步任务对象A
CompletableFuture<Object> futureA = CompletableFuture.supplyAsync(new Supplier<Object>() {
@Override
public Object get() {
return "404";
}
},threadPoolExecutor);
//创建一个B
futureA.thenAcceptAsync(new Consumer<Object>() {
@SneakyThrows
@Override
public void accept(Object o) {
Thread.sleep(500);
System.out.println("我是B");
}
},threadPoolExecutor);
//创建一个C
futureA.thenAcceptAsync(new Consumer<Object>() {
@Override
public void accept(Object o) {
System.out.println("我是C");
}
},threadPoolExecutor);
}
}
Here is a test to see if it is parallelization. Let B sleep for a while, and you can see that output C first and then output B, indicating that it is parallelization.
Because if it is serialized, then even if B sleeps for a while, then C will always wait, and the output order is B, C
1.5 Multitasking combination
public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs);
public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs);
allOf
: Wait for all tasks to complete.
anyOf
: As long as there is one task completed.
1.6 Optimize product details page (business code)
1.6.1 Code before optimization
@Service
@SuppressWarnings("all")
public class ItemServiceImpl implements ItemService {
@Autowired
private ProductFeignClient productFeignClient;
//获取商品详情数据
@Override
public HashMap<String, Object> getItem(Long skuId) {
HashMap<String, Object> resultMap=new HashMap<>();
//获取sku的基本详情和图片列表
SkuInfo skuInfo = productFeignClient.getSkuInfo(skuId);
//获取实时价格
BigDecimal skuPrice = productFeignClient.getSkuPrice(skuId);
//判断
if(skuInfo!=null){
//获取三级分类
BaseCategoryView categoryView = productFeignClient.getCategoryView(skuInfo.getCategory3Id());
//获取销售属性和选中状态
List<SpuSaleAttr> spuSaleAttrListCheckBySku = productFeignClient.getSpuSaleAttrListCheckBySku(skuId, skuInfo.getSpuId());
//获取商品切换数据
Map skuValueIdsMap = productFeignClient.getSkuValueIdsMap(skuInfo.getSpuId());
//获取海报信息
List<SpuPoster> spuPosterBySpuId = productFeignClient.findSpuPosterBySpuId(skuInfo.getSpuId());
resultMap.put("categoryView",categoryView);
resultMap.put("spuSaleAttrList",spuSaleAttrListCheckBySku);
resultMap.put("valuesSkuJson", JSON.toJSONString(skuValueIdsMap));
resultMap.put("spuPosterList",spuPosterBySpuId);
}
//获取平台信息
List<BaseAttrInfo> attrList = productFeignClient.getAttrList(skuId);
//处理数据符合要求 List Obj key attrName value attrValue
List<Map<String, String>> spuAttrList = attrList.stream().map(baseAttrInfo -> {
Map<String, String> map = new HashMap<>();
map.put("attrName", baseAttrInfo.getAttrName());
map.put("attrValue", baseAttrInfo.getAttrValueList().get(0).getValueName());
return map;
}).collect(Collectors.toList());
//存储数据
resultMap.put("skuInfo",skuInfo);
resultMap.put("price",skuPrice);
resultMap.put("skuAttrList",spuAttrList);
return resultMap;
}
}
1.6.2 Asynchronous orchestration using CompletableFuture
Configure the thread pool:
@Configuration
public class ThreadPoolConfig {
/**
* 核心线程数
* 最大线程数
* 空闲存活时间
* 时间单位
* 阻塞队列
* 默认:
* 线程工厂
* 拒绝策略
* @return
*/
@Bean
public ThreadPoolExecutor threadPoolExecutor(){
return new ThreadPoolExecutor(
50,
500,
30,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10000)
);
}
}
Implement class transformation:
@Service
@SuppressWarnings("all")
public class ItemServiceImpl implements ItemService {
@Autowired
private ProductFeignClient productFeignClient;
@Autowired
private ThreadPoolExecutor executor;
//获取商品详情数据
@Override
public HashMap<String, Object> getItem(Long skuId) {
HashMap<String, Object> resultMap=new HashMap<>();
CompletableFuture<SkuInfo> skuInfoCompletableFuture = CompletableFuture.supplyAsync(new Supplier<SkuInfo>() {
@Override
public SkuInfo get() {
//获取sku的基本详情和图片列表
SkuInfo skuInfo = productFeignClient.getSkuInfo(skuId);
resultMap.put("skuInfo", skuInfo);
return skuInfo;
}
}, executor);
CompletableFuture<Void> skuPriceCompletableFuture = CompletableFuture.runAsync(new Runnable() {
@Override
public void run() {
//获取实时价格
BigDecimal skuPrice = productFeignClient.getSkuPrice(skuId);
resultMap.put("price", skuPrice);
}
}, executor);
//判断
CompletableFuture<Void> categoryViewCompletableFuture = skuInfoCompletableFuture.thenAcceptAsync(new Consumer<SkuInfo>() {
@Override
public void accept(SkuInfo skuInfo) {
//获取三级分类
BaseCategoryView categoryView = productFeignClient.getCategoryView(skuInfo.getCategory3Id());
resultMap.put("categoryView",categoryView);
}
}, executor);
CompletableFuture<Void> spuSaleAttrListCheckBySkuCompletableFuture = skuInfoCompletableFuture.thenAcceptAsync(new Consumer<SkuInfo>() {
@Override
public void accept(SkuInfo skuInfo) {
//获取销售属性和选中状态
List<SpuSaleAttr> spuSaleAttrListCheckBySku = productFeignClient.getSpuSaleAttrListCheckBySku(skuId, skuInfo.getSpuId());
resultMap.put("spuSaleAttrList",spuSaleAttrListCheckBySku);
}
}, executor);
CompletableFuture<Void> skuValueIdsMapCompletableFuture = skuInfoCompletableFuture.thenAcceptAsync(new Consumer<SkuInfo>() {
@Override
public void accept(SkuInfo skuInfo) {
//获取商品切换数据
Map skuValueIdsMap = productFeignClient.getSkuValueIdsMap(skuInfo.getSpuId());
resultMap.put("valuesSkuJson", JSON.toJSONString(skuValueIdsMap));
}
}, executor);
CompletableFuture<Void> findSpuPosterBySpuIdCompletableFuture = skuInfoCompletableFuture.thenAcceptAsync(new Consumer<SkuInfo>() {
@Override
public void accept(SkuInfo skuInfo) {
//获取海报信息
List<SpuPoster> spuPosterBySpuId = productFeignClient.findSpuPosterBySpuId(skuInfo.getSpuId());
resultMap.put("spuPosterList",spuPosterBySpuId);
}
}, executor);
CompletableFuture<Void> attrListCompletableFuture = CompletableFuture.runAsync(new Runnable() {
@Override
public void run() {
//获取平台信息
List<BaseAttrInfo> attrList = productFeignClient.getAttrList(skuId);
//处理数据符合要求 List Obj key attrName value attrValue
List<Map<String, String>> spuAttrList = attrList.stream().map(baseAttrInfo -> {
Map<String, String> map = new HashMap<>();
map.put("attrName", baseAttrInfo.getAttrName());
map.put("attrValue", baseAttrInfo.getAttrValueList().get(0).getValueName());
return map;
}).collect(Collectors.toList());
//存储数据
resultMap.put("skuAttrList", spuAttrList);
}
}, executor);
//多任务组合 -- 所有的异步任务执行完成才是完成
CompletableFuture.allOf(
skuInfoCompletableFuture,
skuPriceCompletableFuture,
categoryViewCompletableFuture,
spuSaleAttrListCheckBySkuCompletableFuture,
skuValueIdsMapCompletableFuture,
findSpuPosterBySpuIdCompletableFuture,
attrListCompletableFuture
).join();
return resultMap;
}
}
Determine which API to call based on whether there is a return value, and then check whether there are dependencies. Several of them depend on SkuInfo, so use skuInfoCompletableFuture to create.
We need to wait for each task to be executed before returning, so finally use
allOf
the method to combine multiple tasks.
1.6.3 Test whether the function is normal
This kind of asynchronous effect is actually better tested in a high-concurrency environment. We can verify whether the function is normal here.
Visit the product detail page:
View data in Redis
It can be seen that 6 keys are cached. Since our price is a real-time price, the database is always checked. Do not use the cache.