使用 java 21,只需在虚拟线程中执行即可将阻塞 IO 代码转换为非阻塞代码。
我应该简单地包装返回
InputStream
的 HTTP 调用(如方法 nonBlockingA
中),还是在虚拟线程中执行 InputStream
的读取和反序列化会更有效(如方法 中) nonBlockingB
)?
也就是说,
InputStream
的读取是不是一个阻塞IO操作?
请记住,响应可能非常大,可能包含超过 500,000 个字符串。 我也不确定所使用的库是否使用任何 ThreadLocals,不建议使用虚拟线程
@SuppressWarnings("unchecked")
class Request {
private final ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor();
private CloseableHttpClient httpApacheClient;
List<String> nonBlockingA() throws Exception {
InputStream bigInputStream = executorService.submit(this::getResponse).get();
return deserialize(bigInputStream);
}
List<String> nonBlockingB() throws Exception {
return executorService.submit(() -> {
InputStream bigInputStream = getResponse();
return deserialize(bigInputStream);
}).get();
}
private InputStream getResponse() throws IOException {
return httpApacheClient.execute(new HttpGet("http://random/names/size/500000")).getEntity().getContent();
}
private static List<String> deserialize(InputStream body) throws IOException {
try (InputStreamReader reader = new InputStreamReader(body, UTF_8)) {
return new Gson().fromJson(reader, List.class);
}
}
}
您的变体之间没有显着差异。任何返回完整构造结果的方法(如
List<String>
)始终是同步或阻塞方法,因为无法等待异步或外部评估的任何所需结果来构造最终结果。
异步方法必须返回
Future
或类似类型的 Promise 对象,这允许它们在计算实际结果之前返回。这就要求调用者能够处理这种结果。当调用者必须向其调用者返回完全构造的结果时,它会让您回到第一个方向,因为它再次需要阻塞等待。
因此,这种传统的异步处理需要将所有处理步骤实现为异步操作,链接回调以将实际计算推迟到输入可用时。
虚拟线程的要点在于它们根本不需要您编写这种传统的异步方法。您可以以同步方式编写操作。继续以您为例,
List<String> straightForward() throws IOException {
try(InputStream bigInputStream = getResponse()) {
return deserialize(bigInputStream);
}
}
在虚拟线程中调用代码是“调用者的责任”,以获得好处。或者,也许更好的说法是调用者可以选择同步使用您的方法或在虚拟线程中使用您的方法。但也有可能调用者以普通同步方式使用您的方法,但调用者的调用者将调用安排在虚拟线程中。 在最好的情况下,99% 的代码不处理线程或异步 API,而是直接以同步方式编写。只有剩下的 1% 必须在虚拟线程中安排调用,例如 Web 服务器为每个请求创建一个虚拟线程等。