Java 什么';使用Future而不使用多线程有什么意义?

Java 什么';使用Future而不使用多线程有什么意义?,java,multithreading,asynchronous,future,completable-future,Java,Multithreading,Asynchronous,Future,Completable Future,我继承了一些代码,没有留下任何原始开发人员。代码使用了大量的CompletableFuture,这是我第一次使用它,所以我仍然在努力解决它。据我所知,(Completable)Future通常与一些多线程机制一起使用,这些机制允许我们在执行耗时的任务时做一些其他事情,然后通过Future获取结果。例如: 当然,代码要复杂得多,在出现错误时会返回异常completedfuture,但是有可调用的、无可运行的、无执行器、无支持同步()无多线程迹象。我错过了什么?在单线程上下文中使用未来有什么意义?E

我继承了一些代码,没有留下任何原始开发人员。代码使用了大量的CompletableFuture,这是我第一次使用它,所以我仍然在努力解决它。据我所知,
(Completable)Future
通常与一些多线程机制一起使用,这些机制允许我们在执行耗时的任务时做一些其他事情,然后通过Future获取结果。例如:


当然,代码要复杂得多,在出现错误时会返回
异常completedfuture
,但是有
可调用的
、无
可运行的
、无
执行器
、无
支持同步()
无多线程迹象。我错过了什么?在单线程上下文中使用
未来
有什么意义?

Executor服务实现通常管理线程。我使用了ThreadPoolExecutor,它正是这样做的。您注释掉了代码使用的ExecutorService。

是的,现在代码中没有使用多线程。看起来有人打算以这样一种方式编写单线程代码,如果开发人员后来决定使用多线程,那么只需

delegate2()

方法应该修改。

对于存在异步编程的情况,未来是至关重要的。异步编程的最大优点之一是它允许您使用单个线程编写非常高效的代码

此外,期货往往是一个全有或全无的命题。如果你想写异步代码,你必须自上而下地写,即使不是每个方法都做异步的事情

例如,您想编写一个单线程HTTP服务器,像或。服务器的顶层(此处的伪代码非常宽松)可能类似于:

while (true) {
  if (serverSocket.ready()) {
    connection = serverSocket.accept();
    futures.add(server.serve(connection));
  }
  for (Future future : futures) {
    if (future.isDone()) {
      Object result = future.get();
      sendResult(result);
    }
  }
  //Some kind of select-style wait here
}
只有一个线程,但任何时候发生通常需要等待的操作(从数据库、文件读取、请求读取等),它都会使用未来,并且不会阻止一个线程,因此您有一个高性能的单线程HTTP服务器

现在,想象一下,如果应用程序的最高级别与上面一样,并且某个非常低级别的请求必须从文件中读取某些内容,会发生什么情况。该文件读取将产生一个未来。如果你中间的所有层都没有处理未来,那么你将不得不阻止,这将挫败你的目的。这就是为什么我说未来往往是全有或全无

所以我的猜测是:

  • 你的朋友目前做了一些异步的事情,而你还没有捕捉到(他是否从文件、数据库或任何东西中读取过数据?如果是,他是否正在阻止?)
  • 他计划有一天做一些异步的事情,并想为此做计划
  • 他花了很多时间在其他异步框架中,并且逐渐喜欢上了这种风格,即使他没有正确地使用它

  • 异步代码的要点是延迟延续代码

    最常见的场景是I/O,即不等待操作完成,而是说“完成您的工作并在完成时通知我”,或者更常见的是,“完成您的工作并在完成后执行此操作”

    这根本不意味着线程。从任何设备读取数据,无论是网卡还是硬盘驱动器,通常都会从设备向CPU发送某种信号或中断。您可以同时使用CPU。“通知我”在较低级别的代码中更为常见,在较低级别的代码中,您实现了一个调度循环或调度器;“执行此操作”在更高级别的代码中更为常见,您可以使用已建立的库或框架为您分派和/或调度

    不太常见的场景包括在不阻塞线程的情况下延迟执行(想想计时器与
    thread.sleep()
    )和拆分工作。实际上,拆分工作在多个线程中非常常见,在多个线程中,您可以用一点开销来提高性能,但在单个线程中就不是这样了,因为开销只是开销


    作为示例,您提供的代码只是构建已完成的
    CompletableFuture
    s,无论是成功构建还是异常构建,都是非真正异步的异步代码开销的一部分。也就是说,您仍然必须遵循定义的异步样式,在这种情况下,即使可以立即提供结果,也需要为结果分配少量内存

    这可能会在每秒数千次调用时变得明显,或者在有几十个线程的每个线程中每秒数百次调用时变得明显


    有时,您可以通过预定义已完成的未来来进行优化,例如,
    null
    0
    1
    -1
    、空数组/列表/流,或者您可能在您的域中特定的任何其他非常常见甚至固定的结果。类似的方法是缓存包装未来,而不仅仅是结果,而结果保持不变。但我建议您在这样做之前先进行概要分析,您可能会过早地优化一些可能不是瓶颈的东西。

    是否涉及UI feamework?如果主线程计算东西,很多东西会变慢。@ChristianKuetbach不,没有。但是如果有呢?你说的“主线”是什么意思?我的观点是,根据我的理解,在上面的方法中,一切都是“主线程”。我不是一个Android开发人员,但我见过很多Android问题,其中“主线程”的含义似乎与我通常认为的主线程不同。更像Swing的事件调度线程。我指的是事件调度线程。您确定不涉及对象代理或字节码编织,这实际上会使一些方法调用在不同的线程上运行吗?例如,Spring有
    @Async
    注释,它告诉我们在immed时在配置的线程池中运行一个方法
    public Object serve(Object input) {
        Object result = delegate1(input);
        return result;
    }
    
    private Object delegate1(Object input) {
        // Do things
        return delegate2(input);
    }
    
    private Object delegate2(Object input) {
        return new Object();
    }
    
    delegate2()
    
    while (true) {
      if (serverSocket.ready()) {
        connection = serverSocket.accept();
        futures.add(server.serve(connection));
      }
      for (Future future : futures) {
        if (future.isDone()) {
          Object result = future.get();
          sendResult(result);
        }
      }
      //Some kind of select-style wait here
    }