Executors.newWorkStealingPool是Java 8为简化ForkJoinPool创建提供的工具方法,它默认会根据当前设备的CPU核心数创建对应数量的并行线程,底层基于ForkJoinPool实现,核心特性就是工作窃取,也就是空闲线程会从其他忙碌线程的任务队列尾部窃取任务来执行,以此提升整体任务处理效率。
Executors.newWorkStealingPool 基本用法
该方法有两个重载形式,无参版本会自动获取运行时可用的处理器核心数作为并行级别,带参版本可以手动指定并行级别。创建得到的线程池是ForkJoinPool的实例,因此可以直接提交ForkJoinTask类型的任务,也可以提交普通的Runnable或Callable任务。
下面是一个简单的使用示例,提交一个Callable任务并获取返回结果:
import java.util.concurrent.*;
public class WorkStealingPoolDemo {
public static void main(String[] args) throws Exception {
// 创建默认并行级别的ForkJoin线程池
ExecutorService executor = Executors.newWorkStealingPool();
// 提交普通Callable任务
Future<String> future = executor.submit(() -> {
Thread.sleep(1000);
return "任务执行完成,执行线程:" + Thread.currentThread().getName();
});
// 获取任务结果
System.out.println(future.get());
executor.shutdown();
}
}
ForkJoin 工作窃取的核心逻辑
ForkJoinPool中的每个工作线程都有自己的双端任务队列,线程正常执行任务时会从自己队列的头部取任务执行。当某个线程的任务队列为空时,它就会成为窃取线程,去其他线程的任务队列尾部窃取任务,这样能减少线程之间的竞争,因为普通线程取任务从头部,窃取线程取任务从尾部,操作的位置不同。
工作窃取的优势在于,当任务可以拆分成多个子任务时,拆分出的子任务会放到当前线程的队列中,其他空闲线程可以窃取这些子任务执行,充分利用多核CPU的计算能力,避免部分线程忙碌部分线程空闲的情况。
实战验证工作窃取过程
我们可以通过自定义ForkJoinTask,在任务执行时打印线程信息,同时构造多个不同耗时的任务,观察是否有线程窃取了其他线程的任务。下面的示例会创建多个耗时不同的计算任务,提交到线程池后,观察线程的执行情况。
import java.util.concurrent.*;
public class WorkStealVerify {
// 自定义ForkJoinTask,打印执行线程和任务信息
static class CustomTask extends RecursiveTask<Integer> {
private final int taskId;
private final int costTime;
public CustomTask(int taskId, int costTime) {
this.taskId = taskId;
this.costTime = costTime;
}
@Override
protected Integer compute() {
String threadName = Thread.currentThread().getName();
System.out.println("任务" + taskId + "开始执行,执行线程:" + threadName + ",预计耗时:" + costTime + "ms");
try {
Thread.sleep(costTime);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("任务" + taskId + "执行完成,执行线程:" + threadName);
return taskId;
}
}
public static void main(String[] args) throws Exception {
// 创建并行级别为2的ForkJoin线程池,方便观察窃取过程
ForkJoinPool pool = (ForkJoinPool) Executors.newWorkStealingPool(2);
// 提交4个任务,前两个耗时短,后两个耗时长
CustomTask task1 = new CustomTask(1, 200);
CustomTask task2 = new CustomTask(2, 200);
CustomTask task3 = new CustomTask(3, 1000);
CustomTask task4 = new CustomTask(4, 1000);
pool.invoke(task1);
pool.invoke(task2);
pool.invoke(task3);
pool.invoke(task4);
// 等待所有任务执行完成
pool.awaitQuiescence(3, TimeUnit.SECONDS);
pool.shutdown();
}
}
运行上述代码后,你会发现两个工作线程会先执行任务1和任务2,执行完成后,这两个线程会去窃取任务3和任务4来执行,原本任务3和任务4可能在其中一个线程的队列中,另一个空闲线程会窃取队列尾部的任务,最终四个任务都会被两个线程执行完成,这就是工作窃取的实际表现。
注意事项
- Executors.newWorkStealingPool创建的线程池是ForkJoinPool的实例,适合处理可以拆分的CPU密集型任务,不适合处理大量IO阻塞任务,否则会阻塞工作线程,降低窃取效率。
- 提交到该线程池的任务如果是ForkJoinTask类型,拆分出的子任务会放到当前线程的队列中,更容易触发工作窃取,普通Runnable或Callable任务也会进入线程队列,但拆分逻辑需要开发者自己实现。
- 不要随意设置过高的并行级别,并行级别超过CPU核心数反而会增加线程上下文切换的开销,一般默认使用无参方法即可。
总结
Executors.newWorkStealingPool是ForkJoinPool的便捷封装,其核心价值就是内置了工作窃取机制,通过空闲线程窃取忙碌线程的任务队列尾部任务,提升多核CPU的利用率。通过实际的代码示例,我们可以清晰观察到工作窃取的发生过程,在后续开发中,处理大批量可拆分的并发任务时,可以优先考虑使用该线程池来提升任务处理效率。
Executors.newWorkStealingPoolForkJoinPool工作窃取线程池并发编程修改时间:2026-07-10 21:30:29