工作窃取不加速单任务,而是通过防止cpu空转提升整体吞吐量;其核心是双端队列分工:线程lifo执行本地任务(头取)、fifo窃取他人任务(尾取),配合合理拆分阈值与禁用阻塞操作,实现高效负载均衡。

工作窃取本身不加快单个任务的执行速度,它靠的是不让任何 CPU 核心空转,来提升整体吞吐量。关键前提是任务可拆分、无阻塞、粒度合适。
理解双端队列分工:LIFO 执行 + FIFO 窃取
每个工作线程维护自己的双端队列(Deque):
- 自己执行时,从队列头部(top)用 LIFO 方式取任务——最近 fork 的子任务优先,局部性好,缓存友好
- 其他线程来“偷”时,从队列尾部(base)用 FIFO 方式取——拿走最早入队、等待最久的任务,降低整体延迟
- 头尾操作由不同线程主导,天然避免 CAS 竞争和伪共享,吞吐更高
合理设置任务拆分阈值(THRESHOLD)
阈值决定任务是否继续 fork。设得不准,工作窃取就失效:
- 太大:多数子任务直接顺序执行,没机会 fork,也就没任务可被窃取
- 太小:fork/join 调度开销(对象创建、队列插入、上下文切换)反超计算收益
- 纯算术类(如数组累加)可设到 10 万+;含随机内存访问或分支预测失败多的,建议 1 万以内
- 实测看
ForkJoinPool.commonPool().getStealCount():理想窃取次数占总任务数的 5%–30%
善用默认公共池,慎建自定义池
绝大多数 CPU 密集型场景,直接用 ForkJoinPool.commonPool() 就够了:
- 默认并行度 =
Runtime.getRuntime().availableProcessors(),正好匹配理论最优核数 - 它已针对计算密集型做了平衡,比如常见设为核数减 1,留一个给主线程或调度器,减少毛刺
- 只有当你有多个独立大任务流、需要资源隔离(比如 A 流占满池子导致 B 流饿死),才考虑
new ForkJoinPool(parallelism)
避开常见陷阱
这些细节容易让工作窃取变成负优化:
- 任务含 IO 或锁(如数据库查询、synchronized 块)——该用
ThreadPoolExecutor,不是 ForkJoin 的菜 - 用了
RecursiveAction却没在compute()里调用fork()和join(),实际还是单线程跑 - 子任务平均耗时低于 10μs——窃取动作本身的开销可能比执行还高
- 所有子任务耗时高度一致(比如固定 size 拆分 + 均匀计算),导致几乎不发生窃取,退化为普通并行,白担 fork/join 开销










