不使用java.util.Timer:基于ExecutionContext实现延迟Future
最简实现方案
首先纠正你函数签名里的拼写笔误:ExecutoionContext正确拼写为ExecutionContext。
需要明确的是,普通ExecutionContext仅负责提交后立刻触发的任务执行,本身没有内置延迟调度能力,因此实现需要内置一个全局共享的调度组件,完全隔离对客户端的暴露,同时彻底放弃对java.util.Timer的依赖。
核心实现代码
import scala.concurrent.{Promise, Future, ExecutionContext} import scala.util.Success import scala.concurrent.duration.FiniteDuration import java.util.concurrent.{Executors, ScheduledExecutorService, TimeUnit} // 全局共享的单例调度器,仅在类加载时初始化一次 private val sharedScheduler: ScheduledExecutorService = Executors.newSingleThreadScheduledExecutor(runnable => { val worker = new Thread(runnable, "delayed-future-scheduler") worker.setDaemon(true) // 设为守护线程,不阻碍JVM正常退出 worker }) def delayedFuture(delay: FiniteDuration) (implicit ec: ExecutionContext): Future[Unit] = { val promise = Promise[Unit]() // 提交延迟任务,到点直接完成Promise sharedScheduler.schedule( () => promise.complete(Success(())), delay.length, delay.unit ) promise.future }
实现说明
- 全程未使用
java.util.Timer,基于JDK自带的ScheduledExecutorService完成延迟调度,可靠性远高于Timer:单个任务抛出未捕获异常不会导致整个调度线程崩溃,调度精度更高,支持主动关闭 - 调度器为全局单例,不会因每次调用
delayedFuture新建独立线程,完全满足封装要求——客户端不会感知到内部调度组件的存在,只需要按照原有隐式参数规则传入ExecutionContext即可 - 特意将调度线程设为守护线程,避免应用退出时被闲置的调度线程阻塞
常见错误避坑
绝对不要为了“完全复用传入的ExecutionContext做调度”,写阻塞式的实现,比如:
// 错误实现,生产环境严禁使用 def delayedFuture(delay: FiniteDuration)(implicit ec: ExecutionContext) = { val p = Promise[Unit]() ec.execute { () => Thread.sleep(delay.toMillis) p.success(()) } p.future }
这种写法会让EC的工作线程长期被sleep占用,高并发场景下会快速耗尽线程池,导致所有依赖该EC的异步逻辑全部阻塞,是非常典型的性能坑点。
内容的提问来源于stack exchange,提问作者Michael
相关产品推荐
相关产品推荐

