【问题标题】:Future.get cannot block the thread in forkjoinpool/workstealingpoolFuture.get 不能阻塞 forkjoinpool/workstealingpool 中的线程
【发布时间】:2019-05-21 07:54:49
【问题描述】:

我把workstealingpool的大小设置为1,看来future.get()并没有阻塞线程。

@Test
public void runAsyncThenApplyExample() {
    ExecutorService executor = Executors.newWorkStealingPool(1);
    CompletableFuture cf = CompletableFuture.supplyAsync(
            () -> {
                //assertTrue(Thread.currentThread().isDaemon());
                System.out.println("func: " + threadName());
                Callable<Long> callable = () ->stub();
                Future<Long> future = executor.submit(callable);
                try {
                    future.get();  <<<<< **I think this should block the thread**
                } catch (InterruptedException | ExecutionException e) {
                    e.printStackTrace();
                }
                return 1;
            }, executor).thenAccept(s -> {
        System.out.println("accept: " + threadName());
    });
    //assertFalse(cf.isDone());
    System.out.println("main: " + threadName());
    sleep(10);
    assertTrue(cf.isDone());
}

private Long stub() {
    System.out.println("stub: " + threadName());
    return 1L;
}
private String threadName() {
    return Thread.currentThread().getName();
}

输出:

函数:ForkJoinPool-1-worker-3
主要:主要
存根:ForkJoinPool-1-worker-3
接受:ForkJoinPool-1-worker-3

Future.get() 和 stub 似乎使用的是同一个threead。

【问题讨论】:

  • 好吧,它会一直阻塞,直到您的 stub 返回,但这是超快的,所以您不会注意到它。
  • 如果阻塞,为什么get线程和stub线程一样。

标签: java concurrency block future forkjoinpool


【解决方案1】:

Executors.newWorkStealingPool(1); 使用 ForkJoinPool,它有一个未记录的功能,称为补偿线程。

来自http://www.coopsoft.com/ar/CalamityArticle.html(强调我的):

JDK1.8 引入了 CompletableFuture 类。引用 JavaDoc:

“可以显式完成的 Future(设置其值和状态),并且可能包括在完成时触发的相关函数和操作。”

JavaDoc中没有提到的是,当使用带有get()方法的大量依赖函数时,框架会创建“补偿线程”以继续从deques和提交队列中获取应用程序任务.

因此,当您执行 future.get(); 时,它会阻塞,但会创建另一个线程来执行任务。

运行代码时,我得到的输出是:

函数:ForkJoinPool-1-worker-1
主要:主要
存根:ForkJoinPool-1-worker-0
接受:ForkJoinPool-1-worker-1

您还没有显示您的 threadName() 方法可能有错误,因此您看到相同的线程名称(或者您使用不同的 JVM 在这种情况下使用相同的名称,请检查线程ID)?如果不是,请提供将 func 和 stub 输出为相同线程的完整代码。

【讨论】:

  • 我刚刚添加了我的 threadName() 函数。就我而言,似乎函数 get() 被阻止,但在使用“WorkStealing/ForkJoinPool”时线程没有被阻止。如果我使用“newFixedThreadPool”并将大小设置为 1,则函数和线程都被阻塞
  • 对于“补偿线程”,如果它显示一些新的线程名称,这是有道理的。在我的环境中,它总是显示相同的线程,这让我感到困惑。
  • 我转储了线程,看来 Future.get() 和 stub 正在使用同一个线程。
  • @zero 使用newFixedThreadPool(1),您只有一个普通线程,从正在运行的任务向其提交新任务并等待新任务结束显然会阻塞,其行为与预期一致。在您的系统上,您的代码确实使用了一个线程,您使用的是什么 JDK?我在 JDK 8 上对其进行了测试,看起来 Oracle 改进了 ForkJoin 框架,并且在您的系统上它足够聪明,可以为 stub 任务重用相同的线程。
  • 看起来 OpenJDK 11 足够聪明,可以重用同一个线程。所以,我认为 Future.get() 是一个阻塞函数,它只阻塞某种“任务”,但不阻塞当前线程。如果我将“WorkStealingPool/ForkJoinPool”更改为其他线程池,例如 FixedThreadPool,Future.get() 将阻塞函数和当前线程。
【解决方案2】:

Future.get() 块,表示Future.get() 后面的代码在得到Result 之前不会执行。即使在您的输出中,您也可以看到存根首先完成执行。您可以尝试在您的退货声明之前在您的存根中放置 5 分钟左右的睡眠时间,看看会发生什么?

【讨论】:

  • 好像get()函数被阻塞了但是线程没有被阻塞。如果我使用“newFixedThreadPool”并将大小设置为 1,则函数和线程都被阻塞
猜你喜欢
  • 1970-01-01
  • 2020-11-10
  • 1970-01-01
  • 2020-11-07
  • 2019-08-28
  • 1970-01-01
  • 1970-01-01
  • 2014-02-08
  • 1970-01-01
相关资源
最近更新 更多