为什么ForkJoinPool :: invoke()会阻塞主线程?

时间:2018-10-01 13:02:07

标签: java java-8 fork-join forkjoinpool

免责声明:这是我第一次使用Java的Fork-Join框架,因此我不确定100%是否正确使用了它。 Java也不是我的主要编程语言,因此这也可能是相关的。


给出以下SSCCE

import java.util.Arrays;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveAction;

class ForkCalculator extends RecursiveAction
{
    private final Integer[] delayTasks;

    public ForkCalculator(Integer[] delayTasks)
    {
        this.delayTasks = delayTasks;
    }

    @Override
    protected void compute()
    {
        if (this.delayTasks.length == 1) {
            this.computeDirectly();
            return;
        }

        Integer halfway = this.delayTasks.length / 2;

        ForkJoinTask.invokeAll(
            new ForkCalculator(
                Arrays.copyOfRange(this.delayTasks, 0, halfway)
            ),
            new ForkCalculator(
                Arrays.copyOfRange(this.delayTasks, halfway, this.delayTasks.length)
            )
        );
    }

    private void computeDirectly()
    {
        Integer delayTask = this.delayTasks[0];

        try {
            Thread.sleep(delayTask);
        } catch (InterruptedException ex) {
            System.err.println(ex.getMessage());
            System.exit(2);
        }

        System.out.println("Finished computing task with delay " + delayTask);
    }
}

public final class ForkJoinBlocker
{
    public static void main(String[] args)
    {
        ForkCalculator calculator = new ForkCalculator(
            new Integer[]{1500, 1400, 1950, 2399, 4670, 880, 5540, 1975, 3010, 4180, 2290, 1940, 510}
        );

        ForkJoinPool pool = new ForkJoinPool(
            Runtime.getRuntime().availableProcessors()
        );

        pool.invoke(calculator);

        //make it a daemon thread
        Timer timer = new Timer(true);

        timer.scheduleAtFixedRate(
            new TimerTask() {
                @Override
                public void run()
                {
                    System.out.println(pool.toString());
                }
            },
            100,
            2000
        );
    }
}

因此,我创建了一个ForkJoinPool,并向其提交了一些要进行处理的任务。在本示例中,为了简单起见,我将其替换为Thread.sleep()

在我的实际程序中,这是一个很长的任务列表,因此我想定期在标准输出上打印当前状态。我尝试使用预定的TimerTask在单独的线程上执行此操作。

但是,我注意到了一些意料之外的事情:在我的示例中,输出类似于:

Finished computing task with delay 1500
Finished computing task with delay 2399
Finished computing task with delay 1400
Finished computing task with delay 4180
Finished computing task with delay 1950
Finished computing task with delay 5540
Finished computing task with delay 880
.......

这意味着永远不会执行“状态任务”。

但是,如果我修改代码以在最后移动pool.invoke(calculator);,那么它将按预期工作:

java.util.concurrent.ForkJoinPool@59bf63ba[Running, parallelism = 4, size = 4, active = 4, running = 0, steals = 0, tasks = 5, submissions = 0]
Finished computing task with delay 1500
java.util.concurrent.ForkJoinPool@59bf63ba[Running, parallelism = 4, size = 4, active = 4, running = 0, steals = 0, tasks = 5, submissions = 0]
Finished computing task with delay 2399
Finished computing task with delay 1400
java.util.concurrent.ForkJoinPool@59bf63ba[Running, parallelism = 4, size = 4, active = 4, running = 0, steals = 0, tasks = 4, submissions = 0]
Finished computing task with delay 4180
Finished computing task with delay 1950
......

我唯一可以得出的结论是ForkJoinPool::invoke()阻塞了主线程(它仅在池中的所有任务完成后才返回)。

我希望主线程中的代码能够继续执行,而fork-join-pool中的任务是异步处理的。

我的问题是:发生这种情况是因为我使用框架不正确吗?我的代码中需要纠正吗?

我注意到ForkJoinPool的一个构造函数中有一个boolean asyncMode参数,但是从实现中可以看出,这只是在FIFO_QUEUELIFO_QUEUE之间做出决定执行模式(不确定确切的模式):

public ForkJoinPool(
    int parallelism,
    ForkJoinWorkerThreadFactory factory,
    UncaughtExceptionHandler handler,
    boolean asyncMode
) {
    this(checkParallelism(parallelism),
         checkFactory(factory),
         handler,
         asyncMode ? FIFO_QUEUE : LIFO_QUEUE,
         "ForkJoinPool-" + nextPoolId() + "-worker-");
    checkPermission();
}

1 个答案:

答案 0 :(得分:3)

基本上invoke()在返回之前会等待整个任务完成,所以是的,主线程正在阻塞。之后,Timer没有时间执行,因为它在守护程序线程上运行。

您可以简单地使用execute()代替invoke()来异步运行任务。然后,您可以在join()ForkJoinTask等待结果,在此期间Timer将运行:

ForkJoinPool pool = new ForkJoinPool(Runtime.getRuntime().availableProcessors());
pool.execute(calculator);

    //make it a daemon thread
Timer timer = new Timer(true);

timer.scheduleAtFixedRate(new TimerTask() {
        @Override
        public void run() {
            System.out.println(pool.toString());
        }
    }, 100, 2000);

calculator.join(); // wait for computation