CompletableFuture使用
1. 介绍
CompletableFuture
是Java 8中引入的一个类,用于支持异步编程和处理异步任务的结果。它提供了一种简单且强大的方式来处理异步操作,使得编写异步代码更加优雅和灵活。
以下是CompletableFuture
的一些关键特性和用法介绍:
- 异步操作:
CompletableFuture
允许您执行异步操作,即在后台线程中执行任务而不会阻塞主线程。您可以使用CompletableFuture.runAsync()
或CompletableFuture.supplyAsync()
方法来启动异步任务。 - 链式操作:
CompletableFuture
支持链式操作,您可以使用一系列的方法来组合和转换异步任务的结果。这些方法包括thenApply()
、thenAccept()
、thenCompose()
、thenCombine()
等,它们允许您以流畅的方式处理异步任务的结果。 - 异常处理:
CompletableFuture
提供了方法来处理异步任务中的异常情况,例如exceptionally()
、handle()
和whenComplete()
等。这些方法使得在异步任务出现异常时能够进行相应的处理和错误恢复。 - 组合多个任务:
CompletableFuture
提供了多种方法来组合多个异步任务的结果,包括allOf()
、anyOf()
、thenCombine()
等。这些方法允许您在多个任务完成后执行进一步的操作,或者从多个任务中选择一个最先完成的结果。 - 超时和取消:
CompletableFuture
提供了超时和取消异步任务的机制。您可以使用completeOnTimeout()
方法设置任务在超时后自动完成,或者使用cancel()
方法取消任务的执行。 - 并发控制:
CompletableFuture
支持对异步任务进行并发控制,例如使用thenApplyAsync()
方法指定任务在特定的Executor
上执行,或者使用thenCompose()
方法串行执行多个任务。 CompletableFuture
与回调方法:CompletableFuture
还支持通过回调方法来处理异步任务的结果。您可以使用thenApply()
、thenAccept()
、thenRun()
等方法指定回调函数,以便在任务完成时执行相应的操作。
通过这些特性和方法,CompletableFuture
为异步编程提供了强大的工具和灵活性。它可以用于处理并发、异步IO、任务调度等各种场景,使得编写高效和可维护的异步代码变得更加容易。
2.使用
以下逐个介绍CompletableFuture
类中方法
supplyAsync
如果你希望获取异步线程之后的结果,可以使用supplyAsync
方法
// 将20个数扔进List集合 并返回
CompletableFuture<ArrayList<Integer>> future = CompletableFuture.supplyAsync(() -> {
ArrayList<Integer> list = new ArrayList<>();
for (int i = 0; i < 20; i++) {
list.add(i);
}
return list;
});
// 使用get方法获取异步线程结果 此方法会阻塞主线程拿到结果
System.out.println(future.get().toString());
System.out.println("我是主线程");
运行结果
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
我是主线程
runAsync
如果你想异步运行一些后台任务并且不想从任务中返回任何东西,可以使用runAsync
方法
// 执行一个没有返回值的异步线程
// 例如读取文件信息
CompletableFuture.runAsync(()->{
//
try {
BufferedReader reader = new BufferedReader(new FileReader("src/main/resources/data.txt"));
// 演示就不流式读取
reader.lines().forEach(System.out::println);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
});
System.out.println("我是主线程");
Thread.sleep(1000L);
运行结果
我是主线程
aa
bb
cc
dd
ee
thenApply
如果你想拿到上一个异步线程返回的结构进行后续处理并返回结果则可以使用此方法
// supplyAsync 提供一个有返回值的异步操作
// thenApply 可以获取上一个异步线程的结果 并返回结果
// get 方法获取执行结果
CompletableFuture<List<Integer>> result = CompletableFuture.supplyAsync(() -> {
ArrayList<Integer> list = new ArrayList<>();
for (int i = 0; i < 20; i++) {
list.add(i);
}
return list;
}).thenApply(
res ->
// 基于结果集合过滤自己需要的
res.stream().filter(i -> i > 10).collect(Collectors.toList())
);
System.out.println(result.get().toString());
System.out.println("我是主线程");
thenAccept
如果你想拿到上一个异步线程返回的结构进行后续处理不返回任何结果则可以使用此方法
// supplyAsync 提供一个有返回值的异步操作
// thenAccept 可以获取上一个异步线程的结果 不返回结果
CompletableFuture.supplyAsync(() -> {
ArrayList<Integer> list = new ArrayList<>();
for (int i = 0; i < 20; i++) {
list.add(i);
}
return list;
}).thenAccept(System.out::println);
System.out.println("我是主线程");
运行结果
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
我是主线程
complete
complete()其实也是个消费操作,但是与thenRun()不同的是,里面可以可抛出的异常
// supplyAsync 提供一个有返回值的异步操作
// 当执行错误 接收参数为空 异常方法exceptionally被执行
CompletableFuture.supplyAsync(() -> {
int a = 10 / 0;
return "hello";
}).whenComplete((t, action) -> System.out.println(t + "执行完成"))
.exceptionally(t -> {
System.out.println("执行失败:" + t.getMessage());
return "异常";
});
allof-多个 CompletableFuture 组合在一起
CompletableFuture<String> f1 = CompletableFuture.supplyAsync(() -> {
// 模拟业务时长
ThreadUtil.sleep(2000);
System.out.println("future1当前线程:" + Thread.currentThread().getId());
return "用户信息";
});
CompletableFuture<String> f2 = CompletableFuture.supplyAsync(() -> {
System.out.println("future2当前线程:" + Thread.currentThread().getId());
return "商品信息";
});
CompletableFuture<String> f3 = CompletableFuture.supplyAsync(() -> {
System.out.println("future3当前线程:" + Thread.currentThread().getId());
return "会员信息";
});
CompletableFuture.allOf(f1,f2,f3);
System.out.println(f1.join());
System.out.println(f2.join());
System.out.println(f3.join());
3.实际应用场景
设计一个场景,前端请求洞察维度分析后立马返回任务id,后台计算。
创建任务结果表
CREATE TABLE `task_result` (
`task_id` bigint NOT NULL COMMENT '任务id',
`dimension` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '维度',
`result` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '结果',
`create_time` varchar(255) DEFAULT NULL COMMENT '时间',
PRIMARY KEY (`task_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
创建用户表
CREATE TABLE `user_info` (
`user_id` int NOT NULL COMMENT '用户id',
`age` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '年龄 age01:0-17 age02:18-20 age03:21-40 ',
`sex` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '性别 sex0:男 sex1:女',
`city` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '城市 city01:北京 city02:上海 city03:深圳',
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
实体类
@Data
@TableName("user_info")
@Builder
public class UserInfo {
private Integer userId;
private String age;
private String sex;
private String city;
}
@Data
@TableName("task_result")
public class TaskResult {
private Integer taskId;
private String dimension;
private String result;
private String createTime;
}
mapper层
@Repository
public interface UserInfoMapper extends BaseMapper<UserInfo> {
}
service层
public interface UserInfoService extends IService<UserInfo> {
}
@Service
public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> implements UserInfoService {
}
插入1000万测试数据
package com.wl;
import com.wl.pojo.UserInfo;
import com.wl.service.UserInfoService;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
@SpringBootTest
@RunWith(SpringRunner.class)
public class Test {
@Autowired
private UserInfoService userInfoService;
@org.junit.Test
public void test(){
List<UserInfo> data = data();
System.out.println(data.size());
userInfoService.saveBatch(data,1000);
}
private List<UserInfo> data() {
List<UserInfo> list = new ArrayList<>();
List<String> ageList = Arrays.asList("age01", "age02", "age02");
List<String> sexList = Arrays.asList("sex0", "sex1");
List<String> cityList = Arrays.asList("city01", "city02","city03");
for (int i = 0; i < 10000000; i++) {
UserInfo userInfo = UserInfo.builder()
.userId(i)
.age(ageList.get(new Random().nextInt(3)))
.sex(sexList.get(new Random().nextInt(2)))
.city(cityList.get(new Random().nextInt(3)))
.build();
list.add(userInfo);
}
return list;
}
}
查看数据库
service业务代码
package com.wl.service;
import com.alibaba.fastjson.JSON;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wl.mapper.TaskResultMapper;
import com.wl.mapper.UserInfoMapper;
import com.wl.pojo.CountResult;
import com.wl.pojo.TaskResult;
import com.wl.pojo.UserInfo;
import com.wl.utils.MapUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
/**
* @Author NoDreamJava
* @Date 2023-6-16 11:20
* @Version 1.0
*/
@Service
@DS("test")
public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> implements UserInfoService {
@Autowired
private UserInfoMapper userInfoMapper;
@Autowired
private TaskResultMapper taskResultMapper;
@Override
public Long countInfoByDimension(Map<String, String> param) {
long taskId = System.currentTimeMillis();
String dimensionList = param.get("dimension");
for (String dimension : dimensionList.split(",")) {
CompletableFuture.runAsync(()->whenCompletable(taskId,dimension));
}
return taskId;
}
private void test01(String dimension) {
CompletableFuture.runAsync(()->{
CompletableFuture<String> future=CompletableFuture.supplyAsync(() ->
{
List<CountResult> result = userInfoMapper.countInfoByDimension(dimension);
List<Map<Object, Object>> collect = result.stream().map(c -> MapUtils.mapOf(c.getColumnName(), c.getCountNum())).collect(Collectors.toList());
return JSON.toJSONString(collect);
}
);
try {
test(future);
} catch (Exception e) {
e.printStackTrace();
}
});
}
private void test(CompletableFuture<String> future) throws InterruptedException, java.util.concurrent.ExecutionException {
while (true) {
if (future.isDone() && !future.isCancelled()) {
String s = future.get();
if (s == null) {
break;
}
TaskResult taskResult=TaskResult.builder()
.taskId(System.currentTimeMillis())
.result(s).build();
taskResultMapper.insert(taskResult);
break;
}
}
}
private void whenCompletable(long taskId, String dimension) {
// 分维度查询
CompletableFuture.supplyAsync(() -> userInfoMapper.countInfoByDimension(dimension)
).whenComplete(
(result,action) -> {
List<Map<Object, Object>> collect = result.stream().map(c -> MapUtils.mapOf(c.getColumnName(), c.getCountNum())).collect(Collectors.toList());
String format = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
// 拼接参数
TaskResult taskResult=TaskResult.builder().taskId(taskId).dimension(dimension)
.createTime(format)
.result(JSON.toJSONString(collect)).build();
System.out.println(taskResult);
taskResultMapper.insert(taskResult);
}
).join();
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)