线程池-ForkJoinPool

Fork/Join

Fork/Join是Java 7引入的一个并行计算框架,专门用于高效处理可以递归分解的任务。它是ExecutorService接口的一个实现,采用了工作窃取(work-stealing)算法来提高多核CPU的利用率。

核心思想

  1. 分而治之(Divide and Conquer):将大任务拆分成小任务并行处理
  2. 工作窃取(Work-Stealing):空闲线程可以从其他线程的任务队列尾部"偷取"任务执行

主要组件

1. ForkJoinPool

  • 特殊的线程池实现
  • 默认线程数等于处理器核心数(Runtime.getRuntime().availableProcessors())
  • 每个线程维护自己的双端任务队列

2. ForkJoinTask

  • 表示可并行执行的任务
  • 两个主要子类:
    • RecursiveAction:用于没有返回值的任务
    • RecursiveTask:用于有返回值的任务

基本使用模式

1. 继承RecursiveAction(无返回值)

class MyAction extends RecursiveAction {
    @Override
    protected void compute() {
        if (任务足够小) {
            // 直接执行任务
        } else {
            // 拆分任务
            MyAction left = new MyAction(...);
            MyAction right = new MyAction(...);
            // 并行执行子任务
            invokeAll(left, right);
        }
    }
}

2. 继承RecursiveTask(有返回值)

class MyTask extends RecursiveTask<ResultType> {
    @Override
    protected ResultType compute() {
        if (任务足够小) {
            // 直接计算结果并返回
            return result;
        } else {
            // 拆分任务
            MyTask left = new MyTask(...);
            MyTask right = new MyTask(...);
            // 并行执行子任务
            left.fork();
            right.fork();
            // 合并结果
            return combine(left.join(), right.join());
        }
    }
}

工作窃取算法原理

  1. 每个工作线程维护一个双端队列
  2. 线程从自己队列的头部获取任务执行
  3. 当自己队列为空时,从其他线程队列的尾部"偷取"任务
  4. 减少了线程间的竞争,提高了CPU利用率

使用示例

示例1:计算1到n的和

class SumTask extends RecursiveTask<Long> {
    private static final int THRESHOLD = 1000; // 阈值
    private final int[] array;
    private final int start;
    private final int end;

    public SumTask(int[] array, int start, int end) {
        this.array = array;
        this.start = start;
        this.end = end;
    }

    @Override
    protected Long compute() {
        if (end - start <= THRESHOLD) {
            // 小任务直接计算
            long sum = 0;
            for (int i = start; i < end; i++) {
                sum += array[i];
            }
            return sum;
        } else {
            // 大任务拆分
            int middle = (start + end) / 2;
            SumTask left = new SumTask(array, start, middle);
            SumTask right = new SumTask(array, middle, end);
            
            // 并行执行
            left.fork();
            right.fork();
            
            // 合并结果
            return left.join() + right.join();
        }
    }
}

// 使用方式
int[] array = new int[10000];
// 初始化数组...
ForkJoinPool pool = new ForkJoinPool();
long sum = pool.invoke(new SumTask(array, 0, array.length));

示例2:并行排序

class SortTask extends RecursiveAction {
    private static final int THRESHOLD = 1000;
    private final int[] array;
    private final int start;
    private final int end;

    public SortTask(int[] array, int start, int end) {
        this.array = array;
        this.start = start;
        this.end = end;
    }

    @Override
    protected void compute() {
        if (end - start <= THRESHOLD) {
            // 小数组直接排序
            Arrays.sort(array, start, end);
        } else {
            // 拆分数组
            int middle = (start + end) / 2;
            SortTask left = new SortTask(array, start, middle);
            SortTask right = new SortTask(array, middle, end);
            
            // 并行排序
            invokeAll(left, right);
            
            // 合并已排序的子数组
            merge(array, start, middle, end);
        }
    }
    
    private void merge(int[] array, int start, int middle, int end) {
        // 合并逻辑...
    }
}

关键特点

  1. 高效利用多核CPU:特别适合计算密集型任务
  2. 自动负载均衡:工作窃取机制使线程保持忙碌状态
  3. 减少线程竞争:每个线程有自己的任务队列
  4. 递归任务分解:天然适合分治算法

适用场景

  1. 大规模数据处理
  2. 递归算法实现(如快速排序、归并排序)
  3. 数学计算(如矩阵运算)
  4. 图像处理
  5. 任何可以分解为独立子任务的问题

注意事项

  1. 任务拆分不宜过细:任务太小会增加调度开销
  2. 避免阻塞操作:不适合IO密集型任务
  3. 合理设置阈值:根据任务特性调整任务拆分阈值
  4. 避免共享可变状态:任务间应尽量减少共享数据
  5. join()应在fork()之后调用:确保正确的任务执行顺序

性能调优

  1. 调整ForkJoinPool的并行级别(线程数)

    ForkJoinPool pool = new ForkJoinPool(Runtime.getRuntime().availableProcessors() * 2);
    
  2. 根据任务特性选择合适的阈值

  3. 考虑使用带async模式的方法(fork()而不是invokeAll())

  4. 对于复杂任务,考虑使用Phaser等同步工具

Fork/Join框架是Java并发编程中处理可分解任务的强大工具,正确使用可以显著提高多核环境下的程序性能。

posted @ 2023-07-12 10:38  CyrusHuang  阅读(23)  评论(0)    收藏  举报