forkjoinpool 中 recursivetask 高效利用多核的关键在于双端队列结构隔离:本地执行仅操作 top(lifo),窃取仅操作 base(fifo),二者内存路径分离,避免伪共享与 cas 冲突。

ForkJoinPool 中的 RecursiveTask 能高效利用多核,关键在于双端队列(Deque)从结构上隔离了本地执行与窃取操作的内存访问路径——不是靠“减少 CAS 次数”,而是让两类操作天然不碰同一位置。
本地执行端:只动 top,LIFO 保证局部性与低冲突
每个 ForkJoinWorkerThread 持有专属 WorkQueue,其内部用原子整数 top 标记最新任务位置。调用 fork() 时,任务被 push() 到数组尾部(即 top 递增);执行时调用 pop(),也是原子地递减 top 并读取该索引处任务。
- 所有本地操作仅读写 top 变量,不涉及 base 或其他线程可见状态
- 数组索引计算用位运算(
top & (array.length - 1)),无扩容、无重哈希,避免结构变更引发的 ABA 或缓存行失效 - 刚 fork 的子任务紧邻父任务数据,CPU 缓存行复用率高,执行延迟低
窃取端:只动 base,FIFO 保障公平且错开热点
空闲线程尝试窃取时,目标是其他线程队列的头部任务,即 base 所指位置。它通过 CAS 尝试将 base 原子递增(base++),成功后读取原 base 索引处任务。
- 窃取操作只修改 base,且仅在目标队列非空(
top > base)时才发起,失败立即放弃,不自旋 - base 和 top 始终保持分离:正常情况下,base ≤ top;窃取者改 base,本线程改 top,二者指向不同数组区域
- 最老任务在 base,最新任务在 top —— 窃取拿走“冷”任务,本地处理“热”任务,自然形成访问隔离
两端不争一格:伪共享与 CAS 失败被系统性规避
WorkQueue 的 top 和 base 是两个独立的 AtomicInteger 字段,JVM 会确保它们落在不同缓存行(通常已 padding 对齐)。这意味着:
- 本线程更新 top 不会使窃取线程缓存的 base 行失效,反之亦然
- CAS 操作各自作用于不同变量,不会因对方修改导致预期值不匹配而失败
- 没有“多个线程反复 CAS 同一个字段”的场景,彻底避开高竞争下的无限重试循环
错误实践会直接破坏这种隔离
若在 compute() 中混用 poll() 或误用 ArrayDeque 替代 WorkQueue,就等于把本应分离的两端操作强行挤到同一端:
- 本线程 pop() 和窃取者 poll() 都去争 head 指针 → CAS 冲突飙升
- ArrayDeque 的非原子扩容或迭代器机制引入锁或异常,使 work-stealing 完全失效
- 手动加锁同步队列 → 回退成传统线程池模式,失去 ForkJoin 的核心优势










