Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/335.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java ForkJoinPool的工作窃取机制未按预期工作_Java_Multithreading_Threadpool_Forkjoinpool - Fatal编程技术网

Java ForkJoinPool的工作窃取机制未按预期工作

Java ForkJoinPool的工作窃取机制未按预期工作,java,multithreading,threadpool,forkjoinpool,Java,Multithreading,Threadpool,Forkjoinpool,我有以下测试代码 public static void main(String[] args){ ForkJoinPool pool = new ForkJoinPool(2); ForkJoinTask task3 = ForkJoinTask.adapt(() -> { System.out.println("task 3 executing"); for(int i = 0; i < 10; ++i){

我有以下测试代码

public static void main(String[] args){
    ForkJoinPool pool = new ForkJoinPool(2);

    ForkJoinTask task3 = ForkJoinTask.adapt(() -> {
        System.out.println("task 3 executing");
        for(int i = 0; i < 10; ++i){
            System.out.println("task 3 doing work " + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    });

    ForkJoinTask task2 = ForkJoinTask.adapt(() -> {
        try {
            System.out.println("task 2 executing");
            Thread.sleep(5000);
            System.out.println("task 2 finishing");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return null;
    });

    pool.submit(task2);

    ForkJoinTask task1 = pool.submit(() -> {
        System.out.println("task 1 executing");
        pool.submit(task3); // EDIT: Original code was task3.fork();
        System.out.println("task 1 joining task 2");
        task2.join();
        System.out.println("task 1 finished");
    });

    task1.join();
}
publicstaticvoidmain(字符串[]args){
ForkJoinPool池=新的ForkJoinPool池(2);
ForkJoinTask task3=ForkJoinTask.adapt(()->{
System.out.println(“任务3正在执行”);
对于(int i=0;i<10;++i){
System.out.println(“任务3正在工作”+i);
试一试{
睡眠(1000);
}捕捉(中断异常e){
e、 printStackTrace();
}
}
});
ForkJoinTask task2=ForkJoinTask.adapt(()->{
试一试{
System.out.println(“任务2正在执行”);
睡眠(5000);
System.out.println(“任务2完成”);
}捕捉(中断异常e){
e、 printStackTrace();
}
返回null;
});
提交(任务2);
ForkJoinTask task1=池。提交(()->{
System.out.println(“任务1正在执行”);
pool.submit(task3);//编辑:原始代码是task3.fork();
System.out.println(“任务1加入任务2”);
task2.join();
System.out.println(“任务1完成”);
});
task1.join();
}
它基本上将3个任务提交给并行性2的ForkJoinPool,任务2和3长时间运行,任务1等待任务2

标记2个线程t1和t2,其中t1执行task1,t2执行task2

在我的理解中,工作窃取魔法发生在join()调用中,调用线程将从自己的工作队列或其他工作线程的工作队列执行任务。因此,我希望t1执行task1,看到join()调用,然后偷取task3并执行到完成


然而,在实践中,t1并没有对join()调用做任何特殊的处理。Task3仅在task1和task2完成后执行。为什么会这样?

在花了数小时研究了ForkJoinPool和ForkJoinTask的源代码之后,我发现:

如果满足以下两个条件之一,join()将导致线程查找并窃取任务:

  • 正在加入的任务位于当前工作线程工作队列的顶部,在这种情况下,工作线程将继续执行该任务(请参见下文)

  • 另一个工作线程的工作队列中有一个任务,但只有当该工作线程从当前工作线程偷取了一个任务时,当前工作线程才会偷回一个任务并执行它(见下文)

  • 对于第一种情况,我主要是从ForkJoinTask.java中的
    doJoin()
    方法推导出来的,下面是一个说明这种情况的工作测试:

    public static void main(String[] args){
        ForkJoinPool pool = new ForkJoinPool(2);
    
        ForkJoinTask task3 = ForkJoinTask.adapt(() -> {
            System.out.println("task 3 executing on thread " + Thread.currentThread());
            for(int i = 0; i < 10; ++i){
                System.out.println("task 3 doing work " + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
    
        ForkJoinTask task2 = ForkJoinTask.adapt(() -> {
            try {
                System.out.println("task 2 executing on thread " + Thread.currentThread());
                Thread.sleep(5000);
                System.out.println("task 2 finished");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return null;
        });
    
        ForkJoinTask task1 = ForkJoinTask.adapt(() -> {
            System.out.println("task 1 executing on thread " + Thread.currentThread());
            pool.submit(task3);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("task 1 joining task 3");
            task3.join();
            System.out.println("task 1 finished");
        });
    
        pool.submit(task2);
        pool.submit(task1);
    
        task1.join();
    }
    
    Task3和task1在同一工作线程上执行,这是预期的,因为Task3直接提交到thread2的工作队列,因此根据案例1,当task1对其调用join()时,它应该执行

    我根据ForkJoinPool.java中的
    awaitJoin()
    方法推导出了第二种情况,下面是一个说明该情况的工作测试

    public static void main(String[] args){
        ForkJoinPool pool = new ForkJoinPool(2);
    
        ForkJoinTask task3 = ForkJoinTask.adapt(() -> {
            System.out.println("task 3 executing on thread " + Thread.currentThread());
            for(int i = 0; i < 10; ++i){
                System.out.println("task 3 doing work " + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
    
        ForkJoinTask task2 = ForkJoinTask.adapt(() -> {
            try {
                System.out.println("task 2 executing on thread " + Thread.currentThread());
                pool.submit(task3);
                Thread.sleep(5000);
                System.out.println("task 2 finished");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return null;
        });
    
        ForkJoinTask task1 = ForkJoinTask.adapt(() -> {
            System.out.println("task 1 executing on thread " + Thread.currentThread());
            pool.submit(task2);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("task 1 joining task 2");
            task2.join();
            System.out.println("task 1 finished");
        });
    
        pool.submit(task1);
    
        task1.join();
        task2.join();
        task3.join();
    }
    
    Task3在thread1上执行,因为task1正在等待task2,这是可能的,因为task2已提交到thread1的工作队列,但由于thread2是免费的,因此该任务可能会成为thread1的窃取者。当thread1看到来自task1的join()调用时,它会查看窃取者(thread2)的工作队列,找到task3,获取并执行它

    还请注意,task1仅在task3之后完成执行,这意味着一旦线程窃取了任务,它必须执行到完成

    对于最初的问题,我在一个非ForkJoinWorkerThread(主线程)中提交了task1和task2,因此工作线程的非工作线程相互窃取,因此第二种情况不适用。此外,由于我在第二个任务上调用了join(),该任务位于thread2的工作队列中,因此第一种情况不适用,因此不会发生窃取

    编辑:
    这决不是java中F/J的答案,如果有任何问题,请指出。事实上,挖掘所有这些细节只会产生更多的问题:也就是说,为什么工作线程不接受任意任务并运行它呢?为什么它必须来自偷窃者或它自己的工作队列?如果您有答案,请发表评论/帖子。

    我看不到task3在上述代码中提交。谢谢您指出这一点。我将task3.fork()更改为pool.submit(task3)。但是,它不会改变我描述的行为Task1将是最后一个完成的,因为它等待task2完成预期的行为。我的问题是为什么thread1在加入task2时不执行task3?您如何知道哪个线程正在执行任务?如果您只向池提交任务,池将管理分配给各个线程。fork-join框架具有意外行为的事实足以让我完全避免它。当我看到它分配的线程比我规定的多时,我就放手了。在这里,你是在告诉我,它并不总是可以预测的偷。我肯定他们有理由,我只是不想听。我宁愿设计一个好的事件驱动系统,也不愿抓住阻塞线程。Fork-Join框架是一个柠檬,你可能是对的。我有大量的任务要处理,所以我需要使用比原始线程更好的方法。如果要使用executor服务,请参阅Executors.newFixedThreadPool(),然后继续。如果你真的有依赖的任务,就使用CompletableFutures。事实上,这应该是一个Android项目。我必须将CompletableFuture移植回CompletableFuture,因为并非所有API级别都支持它。在我的实现中,我将为每个任务启动一个新线程。我目前有相互依赖的任务,所以我确实需要ForkJoinPool,但发现它在所有API级别上都不受支持,所以我也必须实现它。我假设您有一个executor服务。那么,让你的任务发布新的任务应该是非常琐碎的;例如,
    public static void main(String[] args){
        ForkJoinPool pool = new ForkJoinPool(2);
    
        ForkJoinTask task3 = ForkJoinTask.adapt(() -> {
            System.out.println("task 3 executing on thread " + Thread.currentThread());
            for(int i = 0; i < 10; ++i){
                System.out.println("task 3 doing work " + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
    
        ForkJoinTask task2 = ForkJoinTask.adapt(() -> {
            try {
                System.out.println("task 2 executing on thread " + Thread.currentThread());
                pool.submit(task3);
                Thread.sleep(5000);
                System.out.println("task 2 finished");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return null;
        });
    
        ForkJoinTask task1 = ForkJoinTask.adapt(() -> {
            System.out.println("task 1 executing on thread " + Thread.currentThread());
            pool.submit(task2);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("task 1 joining task 2");
            task2.join();
            System.out.println("task 1 finished");
        });
    
        pool.submit(task1);
    
        task1.join();
        task2.join();
        task3.join();
    }
    
    task 1 executing on thread Thread[ForkJoinPool-1-worker-1,5,main]
    task 2 executing on thread Thread[ForkJoinPool-1-worker-2,5,main]
    task 1 joining task 2
    task 3 executing on thread Thread[ForkJoinPool-1-worker-1,5,main]
    task 3 doing work 0
    task 3 doing work 1
    task 3 doing work 2
    task 3 doing work 3
    task 2 finished
    task 3 doing work 4
    task 3 doing work 5
    task 3 doing work 6
    task 3 doing work 7
    task 3 doing work 8
    task 3 doing work 9
    task 1 finished