Java异步是否会阻塞?

7

在搜索了很多有关Java的内容后,我对以下问题感到非常困惑:

  1. Why would I choose an asynchronous method over a multi-threaded method?

  2. Java futures are supposed to be non-blocking. What does non-blocking mean? Why call it non-blocking when the method to extract information from a Future--i.e., get()--is blocking and will simply halt the entire thread till the method is done processing? Perhaps a callback method that rings the church bell of completion when processing is complete?

  3. How do I make a method async? What is the method signature?

    public List<T> databaseQuery(String Query, String[] args){
      String preparedQuery = QueryBaker(Query, args);
      List<int> listOfNumbers = DB_Exec(preparedQuery); // time taking task
      return listOfNumbers;
    }
    

    How would this fictional method become a non blocking method? Or if you want please provide a simple synchronous method and an asynchronous method version of it.


一种选择是启动一个新线程来执行未来的解决方案,但这并不总是可行的。 - Jos
https://dzone.com/articles/javautilconcurrentfutureFuture承诺在未来某个时刻返回结果(或重新抛出异常)。使用isDone()或get(timeout)轮询完成情况。在完成之前,您可以做其他事情或将更多的Futures提交给执行器工作线程。如果您需要一个始终同步的单线程请求-响应,则传统编码可能更适合。您需要创建Callable<ResultObj>实例并将其提交给执行器工作线程。执行器会给您一个Future<ResultObj>承诺者。 - Whome
3个回答

6
我为什么要选择异步方法而不是多线程方法?
异步方法允许您减少线程数量。而不是在阻塞调用中占用线程,您可以发出异步调用,然后在它完成后被通知。这释放了线程以便同时进行其他处理。
编写异步代码可能更加复杂,但好处是提高了性能和内存利用率。
Java futures应该是非阻塞的。什么是非阻塞?为什么称其为非阻塞,当从Future提取信息的方法(即get())是阻塞的,并且将一直停止整个线程直到方法完成处理?也许可以使用回调方法,在处理完成时响铃教堂的钟声?
请查看Java 8中新增的CompletableFuture。相比于Future接口,它是一个更加有用的接口。首先,它允许您将各种回调和转换链接到futures中。您可以设置在future完成后运行的代码。这比在get()调用中阻塞要好得多,正如您所猜测的那样。
例如,给定以下异步读写方法:
CompletableFuture<ByteBuffer> read();
CompletableFuture<Integer> write(ByteBuffer bytes);

你可以像这样从文件中读取并写入套接字:
file.read()
    .thenCompose(bytes -> socket.write(bytes))
    .thenAccept(count -> log.write("Wrote {} bytes to socket.", count)
    .exceptionally(exception -> {
        log.error("Input/output error.", exception);
        return null;
    });

如何将方法变为异步?方法签名是什么?

你需要让它返回一个 Future。

public CompletableFuture<List<T>> databaseQuery(String Query, String[] args);

这时候,方法需要负责在其他线程中执行工作,避免阻塞当前线程。有时你可能已经有了准备好的工作线程。如果没有,你可以使用ForkJoinPool,它可以让后台处理变得非常简单。

public CompletableFuture<List<T>> databaseQuery(String query, String[] args) {
    CompletableFuture<List<T>> future = new CompletableFuture<>();
    Executor executor = ForkJoinPool.commonPool();

    executor.execute(() -> {
        String preparedQuery = QueryBaker(Query, args);
        List<T> list = DB_Exec(preparedQuery); // time taking task
        future.complete(list);
    });
}

4

为什么我会选择异步方法而不是多线程方法?

它们对我来说听起来很相似,除了异步方法似乎会在后台使用一个线程。

Java futures 应该是非阻塞的吗?

非阻塞操作通常使用 Future,但对象本身是阻塞的,只有在等待时才会阻塞。

什么是非阻塞?

当前线程不等待/阻塞。

为什么称其为非阻塞,当从 Future < some-object > 中提取信息的方法即 get() 是阻塞的?

你称它为非阻塞。在后台启动操作是非阻塞的,但如果需要结果,则阻塞是获取此结果的最简单方法。

并且是否会完全停止整个线程,直到方法完成处理?

正确,它会这样做。

也许可以使用回调方法,在处理完成时响铃教堂的钟声?

您可以使用 CompletedFuture,或者只需添加要在末尾执行的任何内容即可。您只需要阻塞当前线程必须完成的任务。

您需要返回 Future,并在等待时执行其他操作,否则使用非阻塞操作没有意义,最好在当前线程中执行它,因为它更简单和更有效率。

您已经拥有同步版本,异步版本将如下:

public Future<List<T>> databaseQuery(String Query, String[] args) {
     return executor.submit(() -> {
         String preparedQuery = QueryBaker(Query, args);
         List<int> listOfNumbers = DB_Exec(preparedQuery); // time taking task
         return listOfNumbers;
     });
}

2
我不是多线程方面的专家,但是为了自己也想尝试回答这些问题。
引用: 为什么我会选择异步方法而不是多线程方法?(我的问题:我相信我读了太多,现在我自己也感到困惑)
多线程是使用多个线程进行工作,除此之外并没有太多其他的内容。一个有趣的概念是,多个线程无法真正并行工作,因此将每个线程分成小块来产生并行工作的假象。

1

一个多线程会有用的例子是实时多人游戏,其中每个线程对应于每个用户。用户A将使用线程A,用户B将使用线程B。每个线程可以跟踪每个用户的活动,并且数据可以在每个线程之间共享。

2

另一个例子是等待长时间的http调用。假设您正在设计一个移动应用程序,用户单击下载一个5 GB的文件。如果您不使用多线程,则用户将被卡在该页面上,无法执行任何操作,直到http调用完成。
需要注意的是,作为开发人员,多线程只是一种设计代码的方式。它增加了复杂性,并不总是必须要做的。
现在来讲异步 vs 同步,阻塞 vs 非阻塞。
这是我从http://doc.akka.io/docs/akka/2.4.2/general/terminology.html找到的一些定义。
异步 vs 同步
一个方法调用被认为是同步的,如果调用者在方法返回值或抛出异常之前无法取得进展。另一方面,异步调用允许调用者在有限的步骤后继续进行,并且方法的完成可以通过某些额外的机制(可能是注册回调、Future 或消息)来表示。
同步 API 可能使用阻塞来实现同步,但这并不是必须的。非常 CPU 密集的任务可能会产生类似阻塞的行为。通常情况下,最好使用异步 API,因为它们保证系统能够取得进展。Actor 本质上是异步的:一个 actor 可以在发送消息后继续进行,而无需等待实际交付发生。
非阻塞 vs 阻塞
我们谈论阻塞(blocking)时,是指一个线程的延迟可能会无限期地延迟其他一些线程。一个很好的例子是资源可以通过互斥使用由一个线程独占。如果一个线程无限期地持有该资源(例如意外运行无限循环),则等待该资源的其他线程将不能进展。相比之下,非阻塞(non-blocking)意味着没有任何线程能够无限期地延迟其他线程。

非阻塞操作优于阻塞操作,因为包含阻塞操作的系统的整体进展并不是显而易见的。

我认为异步与同步更多地涉及到调用的意图,而阻塞与非阻塞则涉及到调用的结果。然而,通常情况下异步与非阻塞配对,同步与阻塞配对也是正确的。


Java的Future应该是非阻塞的?什么是非阻塞?为什么叫它非阻塞,当从Future 中提取信息的方法即get()是阻塞的,并且将会一直停止整个线程,直到方法处理完成?也许可以使用回调方法,在处理完成时响铃教堂钟声?“非阻塞”意味着不会阻塞调用方法的线程。Future在Java中被引入来表示调用的结果,尽管它可能没有完全完成。回到http文件的例子,假设您调用如下所示的方法。
Future<BigData> future = server.getBigFile(); // getBigFile would be an asynchronous method
System.out.println("This line prints immediately");

方法getBigFile会立即返回并继续执行下一行代码。稍后您将能够检索到未来的内容(或被通知内容已准备好)。像Netty、AKKA、Play这样的库/框架广泛使用Futures。
“如何使一个方法异步化?方法签名是什么?”我想这取决于你想做什么。
如果你想快速构建某些东西,你会使用高级函数,如Futures、Actor模型等,这些东西可以让你在多线程环境中高效编程而不会犯太多错误。
另一方面,如果你只是想学习,我建议从低级别的互斥、信号量等多线程编程开始。
如果您只是搜索带有我写的任何关键字的Java异步示例,那么这些代码的示例在谷歌上很多。如果您有其他问题,请告诉我!

在那个多人游戏的例子中,数据共享会不会导致竞争条件或数据不一致? - user4253309
这就是为什么数据结构是否线程安全变得重要的原因。线程安全的数据结构可以从多个线程中安全地访问。此外,如果太多线程访问同一资源,那可能会导致性能不佳。 - jrhee17

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接