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()将导致线程查找并窃取任务:
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