【问题标题】:Why this Future's method is blocking main thread?为什么这个 Future 的方法阻塞了主线程?
【发布时间】:2020-05-10 13:25:06
【问题描述】:
ExecutorService executor = Executors.newFixedThreadPool(2);

Future<Integer> calculate(Integer input) {
    return executor.submit(() -> {
        Thread.sleep(3000);
        return input * input;
    });
}

public static void main(String []args) throws Exception {
    Main m = new Main();
    System.out.println(m.calculate(5).get());
    System.out.println("Main");

我们使用 2 个线程向 Executor 提交 Callable,但是当我告诉 m.calculate(5).get() 时它阻塞了主线程。 所以,我不明白,如果 Future 阻塞了主线程并且不异步运行,我应该何时以及为什么要使用它?

【问题讨论】:

  • 问题不在于Future 本身。 get 方法等待它完成并返回未来的返回值,所以通过调用 get 你明确阻塞了主线程。
  • @BackSlash,表示Future不是为异步任务创建的,没有阻塞?
  • 您似乎忽略了这样一个事实,即阻塞的是对 get() 的调用。在调用get() 之前,您可以做任何您想做的事情Future 所代表的任务将与您正在做的任何其他事情同时运行。
  • @Rarity7- 如果你调用get,它只是非并行的。如果结果还没有准备好,你还期望get 做什么?您可以使用isDone() 检查结果是否已准备就绪

标签: java multithreading future


【解决方案1】:

如果您查看 Future::get 的文档,它会说:“如果需要,等待计算完成,然后检索其结果。”通过调用此方法,您同意等待导致主线程。

你可以通过调用Future::isDone来检查Future是否已经完成,它返回布尔值。

在你的场景中可以这样使用

public static void main(String []args) throws Exception {
    Main m = new Main();
    Future<Integer> futureInt = m.calculate(5);
    // do some other asynchronous task or something in main thread while futureInt is doing its calculations
    // and then call Future::get
    int result = futureInt.get();

见:doc

【讨论】:

  • 表示Future不是为没有阻塞的异步任务创建的?
  • 这意味着 Future 只是你的任务结果的一个占位符,在你调用 Future::get 的时候它可能存在也可能不存在。
【解决方案2】:

Future 确实是一个非常有限的抽象,在更实际的情况下,您应该改用CompletableFutureFuture 是一个相当古老的类(我猜是从 java 1.5 开始的)所以对行业的理解在并发编程领域逐渐演变,

尽管如此,它仍然可以单独使用。

如果不是生成一个 future 并立即调用 get ,我们想生成许多任务并将结果存储在某个列表中:

List<Future<Integer>> futures = new ArrayList<>(10);
for(int i = 0 ; i< 10; i++) {
   futures.add(calculate(<some_integer>));
}
// at this point all futures are running concurrently
for(int i = 0 ; i < 10; i++) {
   futures.get(i).get(); // will either return immediately or we'll block the main thread but the point is that all the calculations will run concurrently
}

【讨论】:

  • 虽然应该提一下,但对于今天你可能会使用流的许多任务:IntStream.range(0, 10).map(this::calculate).collect(Collectors.toList())
  • @Amadán Streams 有不同的用途。虽然这个答案中的代码确实可以替换为 parallel 流。但是,您不能启动并行流、执行其他操作,然后稍后检索流的结果——除非将终端操作调用卸载到另一个线程。
  • @Slaw 你说得对,流不容易并行化批处理和一些TaskRunner的一些不同任务。我仍然认为在大多数情况下,定义一个流然后在某些执行器中将 collect() 操作作为并行任务运行是一个好主意。我想说创建和管理自己的线程很少需要。
猜你喜欢
  • 2020-01-02
  • 1970-01-01
  • 1970-01-01
  • 2021-11-12
  • 1970-01-01
  • 2016-01-11
  • 2013-09-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多