面试题答案
一键面试whenComplete方法实现原理
CompletableFuture
的whenComplete
方法用于在CompletableFuture
完成(无论是正常完成还是异常完成)时,执行一个特定的动作。该方法接收一个BiConsumer
作为参数,当CompletableFuture
状态改变时,会将计算结果(若正常完成)或异常(若异常完成)传递给这个BiConsumer
并执行。它是通过注册回调函数到CompletableFuture
的内部状态机实现的,当CompletableFuture
完成时,会触发这些回调。
可能导致性能问题的点
- 串行执行回调:
whenComplete
注册的回调是顺序执行的,如果有多个回调,并且某些回调执行时间较长,会阻塞后续回调的执行,在高并发场景下影响整体性能。 - 线程上下文切换:若回调任务执行在与
CompletableFuture
计算任务不同的线程,频繁的线程上下文切换会消耗系统资源,降低性能。 - 内存开销:大量的
CompletableFuture
实例及其回调会占用较多内存,可能导致频繁的垃圾回收,影响性能。
优化方案
- 使用异步回调
- 适用场景:适用于回调任务执行时间较长且不依赖
CompletableFuture
计算结果立即返回的场景,例如进行日志记录、数据异步持久化等。 - 实现方式:使用
whenCompleteAsync
方法替代whenComplete
,该方法会将回调任务提交到默认的ForkJoinPool.commonPool()
或指定的Executor
中执行,实现异步执行回调。 - 可能带来的副作用:增加了线程调度和上下文切换的开销,如果回调任务过于短小,这种开销可能得不偿失。此外,使用默认的
ForkJoinPool.commonPool()
可能会导致线程饥饿问题,特别是当任务队列中有大量长时间运行的任务时。
- 适用场景:适用于回调任务执行时间较长且不依赖
- 合并回调
- 适用场景:当多个
whenComplete
回调任务逻辑可以合并时适用,例如多个回调都是对结果进行不同维度的日志记录。 - 实现方式:将多个回调逻辑合并到一个
BiConsumer
中,减少回调的数量,从而减少顺序执行带来的阻塞。 - 可能带来的副作用:代码的可读性可能会降低,因为原本分离的逻辑合并到了一起,维护和调试相对困难。
- 适用场景:当多个
- 自定义线程池
- 适用场景:适用于对线程资源有严格控制和管理需求的场景,比如需要限制线程数量、设置线程优先级等。
- 实现方式:创建一个自定义的
ExecutorService
,并将其作为参数传递给whenCompleteAsync
方法,使得回调任务在自定义线程池中执行。 - 可能带来的副作用:需要自行管理线程池的生命周期,包括线程池的创建、销毁以及处理线程池满等异常情况,增加了代码的复杂性。如果线程池参数设置不合理,可能导致资源浪费或任务执行缓慢。