OkHttp 异步请求的“线程池 + 调度器(Dispatcher)”

1) 用的是什么线程池?

OkHttp 的异步请求由 Dispatcher 驱动,内部默认懒创建一个 ThreadPoolExecutor(近似 Cached 线程池)

  • corePoolSize = 0

  • maximumPoolSize = Int.MAX_VALUE

  • keepAlive = 60s

  • workQueue = SynchronousQueue

  • 线程名形如 "OkHttp Dispatcher"

    这段配置在源码/问题单里都有直接体现。

这意味着:空闲不占线程;并发多时可迅速扩张线程;空闲 60s 自动回收。

另外,OkHttp 5 起引入了一个内部 TaskRunner (处理连接维护、HTTP/2 ping 等内部任务),它自己也持有一个类似配置的 ThreadPoolExecutor(线程名 "OkHttp TaskRunner" )。这是 内部线程池,和请求线程池职责不同。

2) 调度策略(队列与限流)

Dispatcher 不仅有线程池,还有"排队算法"和并发上限:

  • 全局并发上限 :maxRequests,默认 64

  • 同主机并发上限 :maxRequestsPerHost,默认 5

  • 队列:readyAsyncCalls(待执行)、runningAsyncCalls(执行中)、以及 runningSyncCalls(仅做统计)

    当你 Call.enqueue() 时,先进入 readyAsyncCalls;Dispatcher 的 promoteAndExecute() 会在 不超过上述上限 时把任务提交给线程池执行。源码字段与默认值可见。

注意:同步请求(execute())不受 maxRequests 的限制 *,它们在调用方线程里跑,只是被记录在 runningSyncCalls 里做统计。

回调线程:异步请求的 onResponse/onFailure 默认就在 OkHttp 的"Dispatcher"线程里回调,不会切到主线程;如果需要主线程,请自己切换。官方特性文档也说明"异步:在另一条线程回调"。

3) 能否自定义线程池/并发?

可以。你可以给 OkHttpClient 注入自定义 Dispatcher 或直接改并发上限:

ini 复制代码
val executor = java.util.concurrent.ThreadPoolExecutor(
    0, 64, 60, TimeUnit.SECONDS, java.util.concurrent.SynchronousQueue(),
    { r -> Thread(r, "My OkHttp Dispatcher") }
)
val dispatcher = okhttp3.Dispatcher(executor).apply {
    maxRequests = 128
    maxRequestsPerHost = 16
}
val client = OkHttpClient.Builder()
    .dispatcher(dispatcher)
    .build()

官方 API 明确:Dispatcher 内部就是用 ExecutorService 跑任务,你可以自带;另外用 newBuilder() 派生出来的客户端共享连接池与线程池。

进阶:很多人在 JDK 21 想切到 Virtual Threads,也可以把 dispatcher 的执行器替换为 Executors.newVirtualThreadPerTaskExecutor()(社区已有讨论)。

4) 常见坑 & 实战调优

  • 别在拦截器里"套娃"大量新请求

    如果这些新请求都指向同一主机,可能撞上 maxRequestsPerHost 限制,引发"队列里互等"的僵持。必要时提高 per-host 上限或把刷新/鉴权请求切到单独的客户端/调度器

  • 监控排队与执行中数量

    用 queuedCallsCount()、runningCallsCount() 做水位监控与告警。

  • 避免在回调里做重 CPU 工作

    回调发生在 OkHttp 自己的线程池里,长时间阻塞会占满"Dispatcher"线程。把重活丢到你自己的业务线程池。

  • 理解 HTTP/2 并发与线程无关

    HTTP/2 的多路复用是连接内的流并发,不等同于线程数;OkHttp 的线程池只负责"谁来跑这次调用"。(并发与连接池/HTTP/2 的更多细节见官方并发文档。)

  • TaskRunner 是内部服务线程

    它不直接执行你的请求回调,主要跑内部维护任务;不要试图用它来承载业务逻辑。

5) 什么时候该调参?

  • 单主机高 QPS:默认 maxRequestsPerHost=5 往往偏小;可按场景提升到 10~32(结合服务端限流、HTTP/2 复用深度压测后定值)。
  • 大量短请求:保持 Cached 风格线程池(SynchronousQueue + keepAlive=60s)通常足够;如果你希望更强的背压,可把工作队列换成有界队列,并自行处理 RejectedExecutionException。
  • 多 OkHttpClient 实例 :倾向于复用同一个 OkHttpClient 或用 newBuilder() 派生,避免碎片化的连接池/线程池(官方推荐单例/共享)。
相关推荐
boooooooom11 分钟前
Pinia必学4大核心API:$patch/$reset/$subscribe/$onAction,用法封神!
javascript·vue.js·面试
试着19 分钟前
【huawei】机考整理
学习·华为·面试·机试
石去皿32 分钟前
【嵌入式就业6】计算机组成原理与操作系统核心机制:夯实底层基础
c++·面试·嵌入式
闻哥1 小时前
Kafka高吞吐量核心揭秘:四大技术架构深度解析
java·jvm·面试·kafka·rabbitmq·springboot
2501_901147832 小时前
面试必看:优势洗牌
笔记·学习·算法·面试·职场和发展
李日灐2 小时前
C++进阶必备:红黑树从 0 到 1: 手撕底层,带你搞懂平衡二叉树的平衡逻辑与黑高检验
开发语言·数据结构·c++·后端·面试·红黑树·自平衡二叉搜索树
Bella的成长园地2 小时前
为什么c++中的条件变量的 wait() 函数需要配合while 循环或谓词?
c++·面试
小迷糊的学习记录16 小时前
0.1 + 0.2 不等于 0.3
前端·javascript·面试
程序员敲代码吗16 小时前
面试中sessionStorage问题引发深度探讨
面试·职场和发展
源代码•宸17 小时前
大厂技术岗面试之谈薪资
经验分享·后端·面试·职场和发展·golang·大厂·职级水平的薪资