面试题答案
一键面试利用Future接口优化过程
- 创建异步任务:
- 使用
ExecutorService
创建线程池,例如ExecutorService executor = Executors.newFixedThreadPool(10);
。 - 对于库存查询、价格查询等操作,将其封装成
Callable
任务。例如:
Callable<Integer> stockCallable = () -> { // 实际的库存查询逻辑 return stockQuery(); }; Callable<BigDecimal> priceCallable = () -> { // 实际的价格查询逻辑 return priceQuery(); };
- 使用
- 提交任务获取Future对象:
- 通过
executor.submit(callable)
方法提交任务并获取Future
对象。
Future<Integer> stockFuture = executor.submit(stockCallable); Future<BigDecimal> priceFuture = executor.submit(priceCallable);
- 通过
- 汇总结果:
- 在需要返回结果给用户时,通过
Future.get()
方法获取异步任务的执行结果。
try { Integer stock = stockFuture.get(); BigDecimal price = priceFuture.get(); // 汇总结果并返回给用户 return buildResponse(stock, price); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); // 处理异常情况 return handleException(e); }
- 在需要返回结果给用户时,通过
- 关闭线程池:
- 在系统不再需要使用线程池时,调用
executor.shutdown();
关闭线程池。如果希望等待所有任务执行完毕再关闭,可以调用executor.shutdownAndAwaitTermination(5, TimeUnit.SECONDS);
,其中5
和TimeUnit.SECONDS
表示等待5秒。
- 在系统不再需要使用线程池时,调用
可能存在的性能瓶颈及解决方案
- 线程池资源耗尽:
- 瓶颈分析:如果并发请求过多,线程池中的线程全部被占用,新的任务就需要等待,可能导致响应速度变慢。
- 解决方案:
- 合理调整线程池的大小,根据系统的硬件资源(如CPU核心数、内存大小)和业务负载来动态调整线程池的最大线程数。可以使用动态线程池,例如
ThreadPoolExecutor
,通过监控系统负载动态调整核心线程数和最大线程数。 - 采用队列来缓存任务,当线程池满时,新任务可以暂存到队列中等待执行。例如
LinkedBlockingQueue
,但要注意队列大小设置,避免内存溢出。
- 合理调整线程池的大小,根据系统的硬件资源(如CPU核心数、内存大小)和业务负载来动态调整线程池的最大线程数。可以使用动态线程池,例如
- Future.get()阻塞:
- 瓶颈分析:如果在调用
Future.get()
时,任务还未完成,当前线程会被阻塞,可能影响系统的并发性能。 - 解决方案:
- 使用
Future.get(long timeout, TimeUnit unit)
方法设置超时时间,避免无限期阻塞。如果在超时时间内任务未完成,可以返回默认值或者进行相应的错误处理。 - 采用异步回调机制,例如使用
CompletableFuture
,它提供了更灵活的异步处理方式,可以通过thenApply
、thenAccept
等方法在任务完成时进行回调处理,而无需阻塞等待。
- 使用
- 瓶颈分析:如果在调用
- 网络延迟:
- 瓶颈分析:库存和价格查询可能涉及到网络调用,网络延迟会影响整个系统的响应速度。
- 解决方案:
- 使用缓存机制,例如Redis,对于频繁查询且不经常变化的数据进行缓存。在查询时先从缓存中获取数据,如果缓存中没有再进行实际的查询操作。
- 优化网络配置,例如增加带宽、优化网络拓扑结构等,减少网络传输时间。同时,可以采用异步I/O技术,提高网络I/O的效率。