0

0

Java多线程API调用中Future.get()返回null的解决方案

DDD

DDD

发布时间:2025-10-22 12:46:22

|

429人浏览过

|

来源于php中文网

原创

Java多线程API调用中Future.get()返回null的解决方案

本文旨在解决java多线程api调用中`future.get()`方法返回`null`的常见问题。当使用`callable`和`executorservice`并发执行api请求并尝试获取结果时,如果流读取逻辑不当,可能导致获取到的数据为空。文章将详细解释问题根源,并提供使用`stringbuilder`正确聚合api响应的解决方案,确保`future.get()`能返回完整的api数据。

在Java应用程序中,尤其是在需要从外部API并行获取大量数据时,多线程是一个高效的解决方案。java.util.concurrent包提供了强大的工具,如ExecutorService和Callable接口,用于管理并发任务。然而,在使用这些工具时,开发者可能会遇到一些意想不到的问题,例如Future.get()方法返回null。本文将深入探讨这一问题,并提供一个健壮的解决方案。

理解多线程API调用与Future

在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 {

    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 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> futures = pool.invokeAll(tasks);
        shutdownAndAwaitTermination(pool);

        System.out.println("\n--- Fetching Results ---");
        for (Future 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的根源

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的实际响应数据。

Bika.ai
Bika.ai

打造您的AI智能体员工团队

下载

为了正确地获取API响应,我们需要在循环内部将每一行读取到的数据累积起来。

解决方案:使用StringBuilder聚合数据

解决此问题的关键是使用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 {

    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 {

    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 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> futures = pool.invokeAll(tasks);

        // 关闭线程池,确保所有任务完成后资源被释放
        shutdownAndAwaitTermination(pool);

        System.out.println("\n--- Fetching Results ---");
        // 遍历 Future 列表,获取每个任务的结果
        for (Future 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());
                // 根据需要处理中断或执行异常
            }
        }
    }
}

注意事项与最佳实践

  1. 资源管理:在进行网络I/O操作时,务必确保正确关闭资源,如HttpURLConnection和BufferedReader。示例中使用了try-with-resources语句来自动管理BufferedReader,并在finally块中关闭HttpURLConnection,这是推荐的做法。
  2. 错误处理:Future.get()方法可能会抛出InterruptedException和ExecutionException。InterruptedException表示线程在等待结果时被中断,而ExecutionException则封装了Callable任务中抛出的任何异常。需要妥善处理这些异常,以增强程序的健壮性。
  3. 线程池管理:ExecutorService在使用完毕后必须关闭。调用shutdown()方法会阻止新任务提交,但会允许已提交的任务完成。为了确保线程池最终终止,通常会结合awaitTermination()方法,在一定时间内等待任务完成,如果超时则调用shutdownNow()强制关闭。
  4. API响应格式:如果API返回的是JSON或其他结构化数据,StringBuilder累积的字符串可以直接用于解析。对于多行文本响应,可能需要在responseBuilder.append(line)之后添加responseBuilder.append("\n")以保留原始的换行符。对于本例中的JSON API,通常不需要额外的换行符。

总结

Future.get()返回null的问题通常源于Callable任务中数据流读取逻辑的缺陷,即在循环结束后,用于存储最终结果的变量被设置为null。通过引入StringBuilder来累积BufferedReader逐行读取的数据,可以确保在循环结束后返回完整的API响应。结合正确的资源管理、异常处理和线程池关闭策略,我们可以构建出高效且健壮的Java多线程API调用应用程序。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
json数据格式
json数据格式

JSON是一种轻量级的数据交换格式。本专题为大家带来json数据格式相关文章,帮助大家解决问题。

419

2023.08.07

json是什么
json是什么

JSON是一种轻量级的数据交换格式,具有简洁、易读、跨平台和语言的特点,JSON数据是通过键值对的方式进行组织,其中键是字符串,值可以是字符串、数值、布尔值、数组、对象或者null,在Web开发、数据交换和配置文件等方面得到广泛应用。本专题为大家提供json相关的文章、下载、课程内容,供大家免费下载体验。

535

2023.08.23

jquery怎么操作json
jquery怎么操作json

操作的方法有:1、“$.parseJSON(jsonString)”2、“$.getJSON(url, data, success)”;3、“$.each(obj, callback)”;4、“$.ajax()”。更多jquery怎么操作json的详细内容,可以访问本专题下面的文章。

311

2023.10.13

go语言处理json数据方法
go语言处理json数据方法

本专题整合了go语言中处理json数据方法,阅读专题下面的文章了解更多详细内容。

77

2025.09.10

string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

463

2023.08.02

c语言中null和NULL的区别
c语言中null和NULL的区别

c语言中null和NULL的区别是:null是C语言中的一个宏定义,通常用来表示一个空指针,可以用于初始化指针变量,或者在条件语句中判断指针是否为空;NULL是C语言中的一个预定义常量,通常用来表示一个空值,用于表示一个空的指针、空的指针数组或者空的结构体指针。

236

2023.09.22

java中null的用法
java中null的用法

在Java中,null表示一个引用类型的变量不指向任何对象。可以将null赋值给任何引用类型的变量,包括类、接口、数组、字符串等。想了解更多null的相关内容,可以阅读本专题下面的文章。

458

2024.03.01

while的用法
while的用法

while的用法是“while 条件: 代码块”,条件是一个表达式,当条件为真时,执行代码块,然后再次判断条件是否为真,如果为真则继续执行代码块,直到条件为假为止。本专题为大家提供while相关的文章、下载、课程内容,供大家免费下载体验。

95

2023.09.25

Golang 网络安全与加密实战
Golang 网络安全与加密实战

本专题系统讲解 Golang 在网络安全与加密技术中的应用,包括对称加密与非对称加密(AES、RSA)、哈希与数字签名、JWT身份认证、SSL/TLS 安全通信、常见网络攻击防范(如SQL注入、XSS、CSRF)及其防护措施。通过实战案例,帮助学习者掌握 如何使用 Go 语言保障网络通信的安全性,保护用户数据与隐私。

2

2026.01.29

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 3万人学习

C# 教程
C# 教程

共94课时 | 7.9万人学习

Java 教程
Java 教程

共578课时 | 52.8万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号