面试题答案
一键面试异常捕获与处理
- 使用
whenComplete
方法:
CompletableFuture.runAsync(() -> {
// 模拟抛出异常
throw new RuntimeException("任务执行出错");
}).whenComplete((v, ex) -> {
if (ex != null) {
System.err.println("捕获到异常: " + ex.getMessage());
}
});
- 使用
exceptionally
方法:
CompletableFuture.runAsync(() -> {
// 模拟抛出异常
throw new RuntimeException("任务执行出错");
}).exceptionally(ex -> {
System.err.println("捕获到异常: " + ex.getMessage());
return null;
});
- 使用
handle
方法:
CompletableFuture.runAsync(() -> {
// 模拟抛出异常
throw new RuntimeException("任务执行出错");
}).handle((v, ex) -> {
if (ex != null) {
System.err.println("捕获到异常: " + ex.getMessage());
}
return null;
});
高并发场景下的优化策略
- 线程池优化:
- 自定义线程池:在实际项目中,避免使用默认的
ForkJoinPool.commonPool()
,因为它是所有CompletableFuture
共享的,可能会导致线程竞争。例如,在一个电商系统中,商品库存更新、订单处理等异步任务并发量高,可自定义线程池。
- 自定义线程池:在实际项目中,避免使用默认的
ExecutorService executor = Executors.newFixedThreadPool(10);
CompletableFuture.runAsync(() -> {
// 异步任务逻辑
}, executor);
- **合理配置线程池参数**:根据任务类型(CPU密集型、I/O密集型)和系统资源(CPU核心数、内存等)来调整线程池大小。对于I/O密集型任务,如网络请求获取商品详情,可适当增大线程池大小,因为线程在等待I/O时CPU空闲,能利用更多线程处理其他任务。
2. 减少任务粒度:将大的异步任务拆分成多个小任务并行执行。比如在数据分析项目中,对大量数据进行清洗和统计,可按数据块划分任务并行处理,每个小任务使用CompletableFuture.runAsync
执行,最后合并结果。这样能充分利用多核CPU资源,提高整体性能。
3. 结果缓存:如果异步任务的结果是重复使用的,可使用缓存。例如,在一个内容管理系统中,获取文章分类信息的异步任务,可将结果缓存起来,下次请求时直接从缓存获取,减少重复计算和异步执行开销。可以使用Guava Cache
或Caffeine
等缓存框架。
4. 异常处理优化:在高并发场景下,异常处理开销较大。对于可能频繁出现且不影响核心业务的异常,可进行适当忽略或简单记录。例如在一个高并发的日志收集系统中,偶尔的网络波动导致日志发送失败,可简单记录重试次数,达到一定次数后放弃,避免过多的异常处理开销影响整体性能。