jdk 1.8 Fork/Join 框架基本使用

1概述

fork/join框架在Java 7 中引入。该框架通过分治思想,并利用所有可用的处理器来加快并行任务的处理速度。

fork阶段,不断递归的将任务分解为较小的子任务,知道这些子任务足够小并被执行。
join阶段,在任务分解处理完毕后,将各个子任务结果合并。如果任务返回void,就等待子任务完成。

fork/join框架使用ForkJoinPool线程池提高并行执行的效率,该线程管理ForkJoinWorkerThread类型的线程。

2 ForkJoinPool

ForkJoinPool类是fork/join框架核心。ForkJoinPool是ExecutorService接口的一个实现,管理Worker线程,并提供接口获取线程池状态及性能相关的信息。

Worker线程同一个时刻只能执行一个task,但ForkJoinPool不会为每个单独的子任务创建独立的线程。

线程池中的每个线程都有一个属于自己用来存放任务的双端队列。work-stealing算法通过这种存储结构来平衡线程负载。

因此核心的对象有ForkJoinPoolForkJoinWorkerThreadWorkQueueForkJoinTask。关系如下图所示:

在这里插入图片描述
ForkJoinPool包含多个ForkJoinWorkThread与WorkQueue。每一个ForkJoinWorkThread对应一个WorkQueue,WorkQueue内部存放ForkJoinTask。ForkJoinWorkThread与WorkQueue分别持有所在ForkJoinPool对象引用,便于调用相关操作完成任务。每组WorkQueue与ForkJoinWorkThread互相持对方的引用。

2.1 核心对象的交互过程

根据如下代码,设定在顺序执行下,只考虑主要对象的交互,忽略细节操作,如锁如何加,取任务时是否使用了workstealing方法。分析第一次pool.invoke后,仅针对第一个任务,队列什么时候创建,线程是什么时候创建,任务何时被执行,后续又进行了哪些工作。

        ForkJoinPool pool = new ForkJoinPool(2,
                ForkJoinPool.defaultForkJoinWorkerThreadFactory,
                null,
                false);
        Integer result = pool.invoke(new RecursiveRangeTask(0, 10));

在这里插入图片描述

ForkJoinPool externalSubmit(ForkJoinTask<?> task) 阶段: 创建workQueues数组,创建WorkQueue对象,并将第一个task放入workQueue对象中。并将ForkjoinkPool对象引用传递给WorkQueue,并将WorkQueue对象保存在workQueues数组中

ForkJoinPool signalWork(WorkQueue[] ws, WorkQueue q): 尝试创建一个ForkJoinWorkerThread对象

ForkJoinPool createWorker: 创建ForkJoinWorkerThread对象,并将ForkJoinPool对象引用传递给ForkJoinWorkerThread, 在构造函数中ForkJoinWorkerThread(ForkJoinPool pool),调用ForkJoinPool pool.registerWorker(this)将线程和队列关联。

ForkJoinWorkerThread run(): 执行 pool.runWorker(workQueue); 调用pool的runWorker方法从队列中拿出任务执,这里包含了scan方法,其中包括了workstealing任务选择过程。

ForkJoinTask compute(): 使用方最终实现的业务逻辑被执行。

ForkJoinPool execLocalTasks(): 执行完task自己所关联的队列中还有剩余任务时,在尝试执行。任务获取方式与ForkJoinPool构造函数参数asyncMode相关,asyncMode确定了这些任务是以FIFO,还是LIFO形处理。

2.2 work stealing算法

空闲线程尝试从繁忙线程中“窃取”任务。默认情况下,worker线程从所关联的队列头部获取任务。当自己的队列任务取完后,该线程从其它繁忙线程队列的队尾获取任务。
该算法降低不同线程对任务竞争的竞争。也减少了线程去寻找任务的次数。当任务分解完毕后,每个线程都有大量可处理的任务。

2.3 ForkJoinPool实例

Java8中,通过ForkJoinPool的静态方法commonPool来获取ForkJoinPool实例。使用预定义的公共池能减少资源消耗。因为该方式避免了为每个任务创建独立的线程池。

ForkJoinPool commonPool = ForkJoinPool.commonPool();

同样在Java7中也可以通过定义工具类来实现。代码如下:

public static ForkJoinPool forkJoinPool = new ForkJoinPool(2);
ForkJoinPool forkJoinPool = PoolUtil.forkJoinPool;

还可以通过ForkJoinPool的构造方法来定制化线程池,可以指定并发度,线程工厂及异常处理器。上面的例子中线程池的并发度为2.

此外还可以指定其它定制化参数来创建ForkJoinPool

public ForkJoinPool(int parallelism,
                        ForkJoinWorkerThreadFactory factory,
                        UncaughtExceptionHandler handler,
                        boolean asyncMode)

parallelism:并行度,默认值为机器cpu核心数
factory: 工厂对象,用来创建线程
handler:异常处理器
asyncMode:为true,使用FIFO调度模式调度处理剩余任务。false,使用LIFO模式处理任务。

asyncMode 说明:
为了便于观察结果,保证结果唯一,将并行度设置为1。为了观察任务的处理顺序,我们额外增加一个任务,用于输出一个id,这个id以步长为1递增。代码将0到6这段范围内的数字拆分,当范围为1时输出结果。若asyncMode为true,那么id将按任务(FIFO)加入顺序打印,即升序。若为false,则按(LIFO)降序打印。

execLocalTasks方法会根据asyncMode选择执行模式

下面是一段测试asyncMode模式的代码,除了分解的任务,再通过调用fork来将任务加入队列,最后输出。

    @Test
    public void testAsyncMode() {


        ForkJoinPool pool = new ForkJoinPool(1,
                ForkJoinPool.defaultForkJoinWorkerThreadFactory,
                null,
                true);

        pool.invoke(new RecursiveRangeAction(0, 6));
        pool.awaitQuiescence(2L, TimeUnit.SECONDS);
    }

    static class RecursiveRangeAction extends RecursiveAction {
        private static final AtomicInteger ASYNC_TASK_ID = new AtomicInteger();

        private final int start;
        private final int end;

        RecursiveRangeAction(int start, int end) {
            this.start = start;
            this.end = end;
        }

        @Override
        protected void compute() {
            // 当前哪个线程来完成对象分组内的任务
            if (end - start <= 1) {
                System.out.format("%s range [%d-%d] done%n",
                        Thread.currentThread().getName(), start, end);
            } else {
                int mid = (start + end) >>> 1;
                int id = ASYNC_TASK_ID.incrementAndGet();
                
                System.out.format("%1$s [%2$d-%3$d] -< [%3$d-%4$d], fork async task %5$d%n",
                        Thread.currentThread().getName(), start, mid, end, id);


                ForkJoinTask.adapt(() -> {
                    System.out.format("%s async task %d done%n",
                            Thread.currentThread().getName(), id);
                }).fork();

                RecursiveRangeAction a1 = new RecursiveRangeAction(start, mid);
                RecursiveRangeAction a2 = new RecursiveRangeAction(mid, end);
                invokeAll(a1,a2);
            }
        }
    }

asyncMode设置为false, 根据输出的拆分任务id可知,任务最终以后进先出形式(LIFO)处理,输出结果如下:

ForkJoinPool-1-worker-1 [0-3] -< [3-6], fork async task 1
ForkJoinPool-1-worker-1 [0-1] -< [1-3], fork async task 2
ForkJoinPool-1-worker-1 range [0-1] done
ForkJoinPool-1-worker-1 [1-2] -< [2-3], fork async task 3
ForkJoinPool-1-worker-1 range [1-2] done
ForkJoinPool-1-worker-1 range [2-3] done
ForkJoinPool-1-worker-1 [3-4] -< [4-6], fork async task 4
ForkJoinPool-1-worker-1 range [3-4] done
ForkJoinPool-1-worker-1 [4-5] -< [5-6], fork async task 5
ForkJoinPool-1-worker-1 range [4-5] done
ForkJoinPool-1-worker-1 range [5-6] done
ForkJoinPool-1-worker-1 async task 5 done
ForkJoinPool-1-worker-1 async task 4 done
ForkJoinPool-1-worker-1 async task 3 done
ForkJoinPool-1-worker-1 async task 2 done
ForkJoinPool-1-worker-1 async task 1 done

asyncMode设置为true,根据输出的拆分任务id可知,任务最终以后进先出形式(FIFO),输出结果如下:

ForkJoinPool-1-worker-1 [0-3] -< [3-6], fork async task 1
ForkJoinPool-1-worker-1 [0-1] -< [1-3], fork async task 2
ForkJoinPool-1-worker-1 range [0-1] done
ForkJoinPool-1-worker-1 [1-2] -< [2-3], fork async task 3
ForkJoinPool-1-worker-1 range [1-2] done
ForkJoinPool-1-worker-1 range [2-3] done
ForkJoinPool-1-worker-1 [3-4] -< [4-6], fork async task 4
ForkJoinPool-1-worker-1 range [3-4] done
ForkJoinPool-1-worker-1 [4-5] -< [5-6], fork async task 5
ForkJoinPool-1-worker-1 range [4-5] done
ForkJoinPool-1-worker-1 range [5-6] done
ForkJoinPool-1-worker-1 async task 1 done
ForkJoinPool-1-worker-1 async task 2 done
ForkJoinPool-1-worker-1 async task 3 done
ForkJoinPool-1-worker-1 async task 4 done
ForkJoinPool-1-worker-1 async task 5 done

3.ForkJoinTask

ForkJoinTask是在ForkJoinPool中执行任务的类型。ForkJoinTask有两个子类,RecursiveAction及RecursiveTask。RecursiveAction用于没有返回值的任务,RecursiveTask用于有返回值的任务。这两个类均有一个抽象方法compute方法用
来定义任务逻辑。

3.1 RecursiveAction 样例

public class CustomRecursiveAction extends RecursiveAction {
 
    private String workload = "";
    private static final int THRESHOLD = 4;
 
    private static Logger logger = 
      Logger.getAnonymousLogger();
 
    public CustomRecursiveAction(String workload) {
        this.workload = workload;
    }
 
    @Override
    protected void compute() {
        if (workload.length() > THRESHOLD) {
            ForkJoinTask.invokeAll(createSubtasks());
        } else {
           processing(workload);
        }
    }
 
    private List<CustomRecursiveAction> createSubtasks() {
        List<CustomRecursiveAction> subtasks = new ArrayList<>();
 
        String partOne = workload.substring(0, workload.length() / 2);
        String partTwo = workload.substring(workload.length() / 2, workload.length());
 
        subtasks.add(new CustomRecursiveAction(partOne));
        subtasks.add(new CustomRecursiveAction(partTwo));
 
        return subtasks;
    }
 
    private void processing(String work) {
        String result = work.toUpperCase();
        logger.info("This result - (" + result + ") - was processed by "
          + Thread.currentThread().getName());
    }
}

3.2 RecursiveTask 样例

public class CustomRecursiveTask extends RecursiveTask<Integer> {
    private int[] arr;
 
    private static final int THRESHOLD = 20;
 
    public CustomRecursiveTask(int[] arr) {
        this.arr = arr;
    }
 
    @Override
    protected Integer compute() {
        if (arr.length > THRESHOLD) {
            return ForkJoinTask.invokeAll(createSubtasks())
              .stream()
              .mapToInt(ForkJoinTask::join)
              .sum();
        } else {
            return processing(arr);
        }
    }
 
    private Collection<CustomRecursiveTask> createSubtasks() {
        List<CustomRecursiveTask> dividedTasks = new ArrayList<>();
        dividedTasks.add(new CustomRecursiveTask(
          Arrays.copyOfRange(arr, 0, arr.length / 2)));
        dividedTasks.add(new CustomRecursiveTask(
          Arrays.copyOfRange(arr, arr.length / 2, arr.length)));
        return dividedTasks;
    }
 
    private Integer processing(int[] arr) {
        return Arrays.stream(arr)
          .filter(a -> a > 10 && a < 27)
          .map(a -> a * 10)
          .sum();
    }
}

例子中,任务存储在arr数组中。createSubtask方法递归的将任务进行分解,知道任务规模小于给定的阈值。invokeAll方法提交子任务到线程池中,并返回future list。

4.向ForkJoinPool提交任务

可以通过submit() 或 execute() 方法来提交任务,并通过join获取结果二者使用方式一致。

forkJoinPool.execute(customRecursiveTask);
customRecursiveTask.join();
ForkJoinPool forkJoinPool = ForkJoinPool.commonPool();
int[] jobs = new int[]{11,12,13,14,15,16};
ForkJoinTask<Integer> forkJoinTask = forkJoinPool.submit(new CustomRecursiveTask(jobs));
System.out.println(forkJoinTask.fork().get());

也可以通过invoke方法来提交任务并获取返回结果。

        ForkJoinPool forkJoinPool = ForkJoinPool.commonPool();
        int[] jobs = new int[]{11,12,13,14,15,16};
        Integer result = forkJoinPool.invoke(new CustomRecursiveTask(jobs));
        System.out.println(result);

5.总结

  • 一个应用或系统使用一个线程池,避免资源消耗。
  • 如果没有定制化ForkJoinPool的需求,使用默认公共线程池。
  • 分解任务使用阈值设定要合理。
  • 避免任务阻塞

6.参考

[1].Guide to the Fork/Join Framework in Java,https://www.baeldung.com/java-fork-join
[2].What is ForkJoinPool Async mode,
https://stackoverflow.com/questions/5640046/what-is-forkjoinpool-async-mode
[3].ForkJoinPool,jdk 1.8 source code.

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值