【问题标题】:Use CompletableFuture on ForkJoinpool and avoid thread waiting在 ForkJoinpool 上使用 CompletableFuture 并避免线程等待
【发布时间】:2016-08-26 15:09:16
【问题描述】:

您好,我认为使用 CompletableFuture 和默认的 ForkJoinPool 可以优化任务的执行,而不是经典的 ExecutorService,但我错过了一些东西

使用此代码执行需要 1 秒,我有 3 个工作线程:

for (int i = 0; i < 3; i++) {
    final int counter = i;
    listTasks.add(CompletableFuture.supplyAsync(() -> {
        Thread.sleep(1000);
        System.out.println("Looking up " + counter + " on thread " + Thread.currentThread().getName());
        return null;
    }));
}

好的,看起来很正常。

但是使用这段代码,需要 3 秒:

for (int i = 0; i < 9; i++) {
    final int counter = i;
    listTasks.add(CompletableFuture.supplyAsync(() -> {
        Thread.sleep(1000);
        System.out.println("Looking up " + counter + " on thread " + Thread.currentThread().getName());
        return null;
    }));
}

我认为睡眠线程将用于启动其他等待任务,它应该也需要 1 秒。例如,我读过 IO WAINTING 线程状态意味着该线程可以重用于其他任务。我可以用Thread.sleep() 测试这种行为吗?我的测试方法是错误的还是我理解错误?

【问题讨论】:

  • 您的计算机上有多少个 CPU?
  • 嗨,2 个真实的,但我将其设置为强制 3 个工作人员并避免产生线程:System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "3");
  • 很好的问题

标签: java multithreading asynchronous completable-future work-stealing


【解决方案1】:

一个休眠线程不能用来完成另一个线程的工作(特别是一个线程不能为另一个线程休眠)。当第一个线程进入睡眠状态时,只有 CPU 可以切换到第二个线程。

当您向CompletableFuture.supplyAsync() 提供任务时,它会转到ForkJoinPool 的默认实例,该实例的线程数与您计算机的CPU 数一样多。您手动将分配的三个线程设置为默认的ForkJoinPool,因此您的九个任务在它们之间平均分配:每个线程连续执行三个任务。所以,你有三秒钟的结果。

【讨论】:

  • 好的,谢谢睡眠,但是如果我做 IO 喜欢文件写入或 web 服务调用而不是 Thread.sleep,我会得到相同的结果还是会重用等待线程来启动其他 IO/网络请求?
  • 如果你的线程被冻结一秒钟来做一些IO,你会得到完全一样的效果。
  • 在您的情况下,您有三个线程,它们从共享队列中选择可用任务。您将九个任务放入此队列。每个线程接受一个任务,完全执行它(休眠一秒钟),然后才接受另一个任务。但是处理器同时(当你的线程休眠时)有机会切换到另一个线程(包括另一个进程的线程)并做一些有用的工作。
  • 我认为像this这样的非阻塞IO有一些优化
  • 非阻塞 IO (NIO) 的架构与您在代码 sn-ps 中使用的线程池不同。 Thread.sleep() 和简单的 IO 不是模拟非阻塞操作的正确选择。如果您使用来自java.nio 的操作,您可以减少时间,但使用线程池对于该任务将毫无用处。
【解决方案2】:

让我们算一下。如果您有 9 个任务并且每个任务休眠 1 秒并且您有 2 个处理器,那么您一次只能运行 2 个 1 秒休眠。使用 9 个任务运行此程序,您至少经过 3 秒或最多经过 4 秒。

这与非阻塞 IO 不同,因为此 Runnable 线程受 CPU 限制,在完成之前不会放弃 CPU(尽管处于睡眠状态)。如果您查看 RxJava 的 IO 线程池之类的东西,它会为每个任务创建一个线程,这对于 IO 任务(但不是 CPU 绑定任务)是可以接受的。

【讨论】:

  • 同意数学,除非有可能的上下文切换。但是正如您所说,并且 RxJava 似乎与 IO 线程池有关,即使线程在 IO 中被阻塞,它也无法切换上下文。好的this解释了它是如何与servlet一起工作的,确实没有神奇的上下文切换,只有线程池用于阻塞线程
猜你喜欢
  • 2023-01-23
  • 2021-12-26
  • 2022-12-05
  • 1970-01-01
  • 2016-01-23
  • 2017-06-09
  • 1970-01-01
  • 1970-01-01
  • 2015-11-17
相关资源
最近更新 更多