本文主要介绍ExecutorCompletionService的使用技巧及在实际项目中的运用
基本介绍
CompletionService
内部维护了一个阻塞队列,当任务执行结束就把任务执行结果的 Future
对象加入到阻塞队列中。
构造方法
CompletionService
接口的实现类是 ExecutorCompletionService
,这个实现类的构造方法有两个
ExecutorCompletionService(Executor executor)
ExecutorCompletionService(Executor executor, BlockingQueue<Future<V>> completionQueue)
这两个构造方法都需要传入一个线程池,如果不指定 completionQueue
,那么默认会使用无界的 LinkedBlockingQueue
。任务执行结果的 Future
对象就是加入到 completionQueue
中。
接口说明
Future<V> submit(Callable<V> task);
Future<V> submit(Runnable task, V result);
Future<V> take() throws InterruptedException;
Future<V> poll();
Future<V> poll(long timeout, TimeUnit unit) throws InterruptedException;
官方使用说明
查看jdk源码,可以看到主要有两个使用场景
场景一:并发执行任务并处理结果
假设我们有一个任务列表,每个任务返回一个字符串结果,我们想要并发执行这些任务,并打印出所有非空的结果。
static void demo1() {
// 创建固定大小的线程池(实际使用中不建议这样做)
ExecutorService executor = Executors.newFixedThreadPool(4);
CompletionService<String> completionService = new ExecutorCompletionService<>(executor);
// 模拟任务列表
List<Callable<String>> tasks = Arrays.asList(
() -> "result 1",
() -> "result 2",
() -> "result 3",
() -> null
);
try {
// 提交所有任务
for (Callable<String> task : tasks) {
completionService.submit(task);
}
// 处理结果
for (int i = 0; i < tasks.size(); i++) {
Future<String> future = completionService.take(); // 阻塞等待任务完成
String result = future.get(); // 获取结果
if (result != null) {
System.out.println("processed: " + result);
}
}
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
} finally {
executor.shutdown();
}
}
场景二:使用第一个非空结果并取消其他任务
假设我们有多个任务尝试解决同一个问题,我们只需要第一个成功的结果,一旦得到,就取消其他的任务。
static void demo2() {
ExecutorService executor = Executors.newFixedThreadPool(4);
CompletionService<String> completionService = new ExecutorCompletionService<>(executor);
List<Future<String>> futures = new ArrayList<>();
try {
// 提交任务并存储Future
for (int i = 0; i < 5; i++) {
final int index = i;
Callable<String> task = () -> {
// 模拟任务执行时间
Thread.sleep((long) (Math.random() * 100));
return "result " + (index + 1);
};
futures.add(completionService.submit(task));
}
String firstResult = null;
while (firstResult == null && !futures.isEmpty()) {
Future<String> future = completionService.take();
String result = future.get();
if (result != null) {
firstResult = result;
System.out.println("First non-null result: " + firstResult);
break; // 找到第一个非空结果,退出循环
}
}
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
} finally {
// 取消所有未完成的任务
for (Future<String> future : futures) {
if (!future.isDone()) {
future.cancel(true);
}
}
executor.shutdown();
}
}
dubbo中的Forking Cluster
Dubbo 中有一种叫做 Forking
的集群模式,这种集群模式下,支持并行地调用多个查询服务,只要有一个成功返回结果,整个服务就可以返回了。
这即是上面场景二的使用场景
项目使用
在这篇文章,高效分页策略:掌握 LIMIT 语句的正确使用方法与最佳实践
其实是用到了,当时没有写完整,现在再来分析下是怎么使用的。
@Service
public class OrderInitServiceImpl{
@Autowired
private OdsPackOrderDAO odsPackOrderDAO;
@Autowired
private BizCommonService bizCommonService;
public void orderInit() throws InterruptedException {
while(true){
String packOrderCalculationSwitch = redisCluster.get("pack_order_switch");
if(packOrderCalculationSwitch != null && packOrderCalculationSwitch.equals("switch_off")){
break; //查询结束
}
List<OdsPackOrderDO> odsPackOrderDOList = bizCommonService.grabBizDataSlice(3,
TimeUnit.MINUTES, 2000, odsPackOrderDAO, null);
// 对查询出来的odsPackOrderDOList做一些业务逻辑
}
}
}
对查询出来的odsPackOrderDOList
做一些业务逻辑,那么具体是怎么做的呢?
if(CollectionUtils.isNotEmpty(odsPackOrderDOList)){
int odsPackOrderSize = odsPackOrderDOList.size();
//异步生产数据
odsPackOrderDOList
.stream()
.forEach(odsPackOrderDO -> producerExecutor.submit(() -> {
return orderService.buildOrderInitParam(odsPackOrderDO);
}));
//阻塞优先消费
while (odsPackOrderSize > 0) {
try {
List<OrderInitParam> orderInitParamList;
Future<List<OrderInitParam>> resultFuture = producerExecutor.take();
if(resultFuture == null || (orderInitParamList = resultFuture.get()) == null){
continue;
}
consumerExecutor.submit(() -> {
orderService.orderInitExecute(orderInitParamList);
});
} catch (Exception e) {
log.error(e.getMessage(), e);
}finally {
odsPackOrderSize --;
}
}
}
可以看到用到了两个线程池producerExecutor
和consumerExecutor
- producerExecutor:就是
ExecutorCompletionService
类型,负责生产数据 - consumerExecutor:消费数据
总结
合理的利用异步框架可以显著的提升系统性能,除了CompletionService
外,还有个CompletableFuture
接口,下次分享一下它在项目中是怎么使用的。