
本文旨在解决java多线程api调用中`future.get()`方法返回`null`的常见问题。当使用`callable`和`executorservice`并发执行api请求并尝试获取结果时,如果流读取逻辑不当,可能导致获取到的数据为空。文章将详细解释问题根源,并提供使用`stringbuilder`正确聚合api响应的解决方案,确保`future.get()`能返回完整的api数据。
在Java应用程序中,尤其是在需要从外部API并行获取大量数据时,多线程是一个高效的解决方案。java.util.concurrent包提供了强大的工具,如ExecutorService和Callable接口,用于管理并发任务。然而,在使用这些工具时,开发者可能会遇到一些意想不到的问题,例如Future.get()方法返回null。本文将深入探讨这一问题,并提供一个健壮的解决方案。
在Java中,Callable接口代表一个可以返回结果并可能抛出异常的任务。它与Runnable类似,但Runnable不返回结果。ExecutorService则负责管理和执行Callable任务。当一个Callable任务被提交给ExecutorService后,它会返回一个Future对象。Future对象代表异步计算的结果,我们可以通过调用其get()方法来阻塞并获取任务的最终结果。
例如,以下代码片段展示了如何使用Callable和ExecutorService并发地调用API:
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
// Callable 任务定义
class ApiCallTask implements Callable<String> {
private Integer taskId;
public ApiCallTask(int taskId) {
this.taskId = taskId;
}
// 模拟API调用逻辑,这里是问题的核心
public String callApi() throws Exception {
String output = null; // 问题根源:output 在循环结束后可能为 null
HttpURLConnection conn = null;
BufferedReader br = null;
try {
URL url = new URL("https://api.publicapis.org/entries");
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Accept", "application/json");
if (conn.getResponseCode() != 200) {
throw new RuntimeException("Failed : HTTP error code : "
+ conn.getResponseCode());
}
br = new BufferedReader(new InputStreamReader((conn.getInputStream())));
System.out.println("Task " + taskId + ": Data starting to come....");
// 错误逻辑:output 在循环结束后会是 null
while ((output = br.readLine()) != null) {
System.out.println("Task " + taskId + " processing line.");
// 这里没有将读取到的数据累积起来
}
} finally {
if (br != null) {
try { br.close(); } catch (Exception e) { e.printStackTrace(); }
}
if (conn != null) {
conn.disconnect();
}
}
return output; // 循环结束后 output 变为 null
}
@Override
public String call() throws Exception {
return callApi();
}
public Integer getTaskId() {
return taskId;
}
public void setTaskId(Integer taskId) {
this.taskId = taskId;
}
}
// 主执行类
public class MultiThreadedApiCaller {
public static void shutdownAndAwaitTermination(ExecutorService executorService) {
executorService.shutdown();
try {
if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
executorService.shutdownNow();
}
} catch (InterruptedException ie) {
executorService.shutdownNow();
Thread.currentThread().interrupt();
}
}
public static void main(String[] args) throws Exception {
ExecutorService pool = Executors.newFixedThreadPool(5); // 线程池大小
List<ApiCallTask> tasks = new ArrayList<>();
int numberOfTasks = 3;
for (int i = 0; i < numberOfTasks; i++) {
tasks.add(new ApiCallTask(i));
System.out.println("Task added: " + i);
}
List<Future<String>> futures = pool.invokeAll(tasks);
shutdownAndAwaitTermination(pool);
System.out.println("\n--- Fetching Results ---");
for (Future<String> f : futures) {
try {
// 此时 f.get() 可能会返回 null
System.out.println("Result for task: " + f.get());
} catch (Exception e) {
System.err.println("Error getting result: " + e.getMessage());
}
}
}
}运行上述代码,你会发现System.out.println("Result for task: " + f.get());会输出null。
立即学习“Java免费学习笔记(深入)”;
Future.get()返回null的原因在于ApiCallTask中的callApi()方法。在callApi()方法中,我们使用BufferedReader来逐行读取API响应:
String output = null;
while ((output = br.readLine()) != null) {
// 内部处理,但没有将 output 累积起来
}
return output;这段代码的问题在于while ((output = br.readLine()) != null)循环。当br.readLine()返回null时(表示流已到达末尾),循环终止,此时output变量的最后一个赋值就是null。因此,方法最终返回的output值就是null,而不是API的实际响应数据。
为了正确地获取API响应,我们需要在循环内部将每一行读取到的数据累积起来。
解决此问题的关键是使用StringBuilder来有效地聚合从BufferedReader读取到的所有行。StringBuilder是一个可变的字符序列,比String在进行大量字符串拼接操作时效率更高。
以下是修改后的ApiCallTask中的callApi()方法:
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.Callable;
class ApiCallTask implements Callable<String> {
private Integer taskId;
public ApiCallTask(int taskId) {
this.taskId = taskId;
}
// 修正后的API调用逻辑
public String callApi() throws Exception {
StringBuilder responseBuilder = new StringBuilder(); // 使用 StringBuilder 累积响应
HttpURLConnection conn = null;
// 使用 try-with-resources 确保 BufferedReader 自动关闭
try {
URL url = new URL("https://api.publicapis.org/entries");
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Accept", "application/json");
if (conn.getResponseCode() != 200) {
throw new RuntimeException("Failed : HTTP error code : "
+ conn.getResponseCode());
}
// try-with-resources 确保资源自动关闭
try (BufferedReader br = new BufferedReader(new InputStreamReader((conn.getInputStream())))) {
String line;
System.out.println("Task " + taskId + ": Data starting to come....");
while ((line = br.readLine()) != null) {
System.out.println("Task " + taskId + " processing line.");
responseBuilder.append(line); // 将每一行数据追加到 StringBuilder
}
}
} finally {
if (conn != null) {
conn.disconnect();
}
}
return responseBuilder.toString(); // 返回累积的完整响应字符串
}
@Override
public String call() throws Exception {
return callApi();
}
public Integer getTaskId() {
return taskId;
}
public void setTaskId(Integer taskId) {
this.taskId = taskId;
}
}通过上述修改,responseBuilder会累积所有读取到的行,并在循环结束后通过responseBuilder.toString()返回完整的API响应数据。现在,Future.get()将能够正确地获取到API返回的内容。
以下是整合了修正后的ApiCallTask和MultiThreadedApiCaller的完整示例:
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
// Callable 任务定义
class ApiCallTask implements Callable<String> {
private Integer taskId;
public ApiCallTask(int taskId) {
this.taskId = taskId;
}
public String callApi() throws Exception {
StringBuilder responseBuilder = new StringBuilder(); // 使用 StringBuilder 累积响应
HttpURLConnection conn = null;
try {
URL url = new URL("https://api.publicapis.org/entries");
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Accept", "application/json");
if (conn.getResponseCode() != 200) {
throw new RuntimeException("Failed : HTTP error code : "
+ conn.getResponseCode());
}
// 使用 try-with-resources 确保 BufferedReader 自动关闭
try (BufferedReader br = new BufferedReader(new InputStreamReader((conn.getInputStream())))) {
String line;
System.out.println("Task " + taskId + ": Data starting to come....");
while ((line = br.readLine()) != null) {
// 对于API返回的JSON,通常不需要换行符,直接追加即可
responseBuilder.append(line);
}
}
} finally {
if (conn != null) {
conn.disconnect();
}
}
return responseBuilder.toString(); // 返回累积的完整响应字符串
}
@Override
public String call() throws Exception {
return callApi();
}
public Integer getTaskId() {
return taskId;
}
public void setTaskId(Integer taskId) {
this.taskId = taskId;
}
}
// 主执行类
public class MultiThreadedApiCaller {
/**
* 安全关闭 ExecutorService 的实用方法。
* 尝试在给定时间内优雅关闭,超时则强制关闭。
* @param executorService 要关闭的 ExecutorService
*/
public static void shutdownAndAwaitTermination(ExecutorService executorService) {
executorService.shutdown(); // 拒绝新任务,但会完成已提交的任务
try {
// 等待所有任务在 60 秒内完成
if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
executorService.shutdownNow(); // 如果超时,则取消正在执行的任务
}
} catch (InterruptedException ie) {
// 捕获中断异常,强制关闭并恢复中断状态
executorService.shutdownNow();
Thread.currentThread().interrupt();
}
}
public static void main(String[] args) throws Exception {
// 创建一个固定大小的线程池
ExecutorService pool = Executors.newFixedThreadPool(5);
List<ApiCallTask> tasks = new ArrayList<>();
int numberOfTasks = 3; // 模拟执行 3 个 API 调用任务
// 准备任务
for (int i = 0; i < numberOfTasks; i++) {
tasks.add(new ApiCallTask(i));
System.out.println("Task added: " + i);
}
// 批量提交任务并获取 Future 列表
List<Future<String>> futures = pool.invokeAll(tasks);
// 关闭线程池,确保所有任务完成后资源被释放
shutdownAndAwaitTermination(pool);
System.out.println("\n--- Fetching Results ---");
// 遍历 Future 列表,获取每个任务的结果
for (Future<String> f : futures) {
try {
String result = f.get(); // 获取任务结果
// 打印结果,通常API响应会很长,这里只打印前一部分或进行其他处理
System.out.println("Result for task: " + (result != null && result.length() > 100 ? result.substring(0, 100) + "..." : result));
} catch (InterruptedException | ExecutionException e) {
System.err.println("Error getting result: " + e.getMessage());
// 根据需要处理中断或执行异常
}
}
}
}Future.get()返回null的问题通常源于Callable任务中数据流读取逻辑的缺陷,即在循环结束后,用于存储最终结果的变量被设置为null。通过引入StringBuilder来累积BufferedReader逐行读取的数据,可以确保在循环结束后返回完整的API响应。结合正确的资源管理、异常处理和线程池关闭策略,我们可以构建出高效且健壮的Java多线程API调用应用程序。
以上就是Java多线程API调用中Future.get()返回null的解决方案的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号