首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 線程獲取返回值的正確姿勢:從 Runnable 到 Callable 與 Future

     編輯:Java 相關知識

問題背景

在 Java 多線程開發中,Thread 類的 run() 方法返回值類型為 void,無法直接獲取線程執行結果。早期開發者常通過共享變量、回調接口或 Thread.join() 等「曲線救國」方案獲取結果,但這些方式要麼線程不安全、要麼代碼冗餘、要麼阻塞主線程。JDK 5 引入的 Callable 與 Future 機制,從根本上解決了「有返回值的線程」需求。

核心方案:Callable + FutureTask / ExecutorService

1. Callable 接口:帶返回值的任務

@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}
  • 泛型 V 表示任務返回值類型。
  • call() 方法可拋出受檢異常,比 run() 更靈活。

2. Future 接口:異步計算的結果句柄

public interface Future<V> {
    boolean cancel(boolean mayInterruptIfRunning);
    boolean isCancelled();
    boolean isDone();
    V get() throws InterruptedException, ExecutionException;
    V get(long timeout, TimeUnit unit) throws ...;
}
  • get() 會阻塞直到任務完成;帶超時版本可避免無限等待。
  • ExecutionException 封裝了任務內部拋出的異常,需調用 getCause() 獲取真實異常。

3. FutureTask:Runnable 與 Future 的雙重身份

FutureTask 實現了 RunnableFuture 接口,既可作為任務提交給 ExecutorService,又可直接作為 Thread 構造參數。

完整代碼示例:模擬發送手機驗證碼

步驟 1:定義 Callable 任務

public class SmsTask implements Callable<String> {
    private final String phone;
    private final String name;

    public SmsTask(String name, String phone) {
        this.name = name;
        this.phone = phone;
    }

    @Override
    public String call() throws Exception {
        // 構建請求 URL
        String urlStr = "https://www.example.com/sms/send?mobile=" + phone + "&flow=t";
        URL url = new URL(urlStr);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod("GET");
        conn.setConnectTimeout(5000);
        conn.setRequestProperty("User-Agent", "Mozilla/5.0...");

        // 發送請求並解析響應
        if (conn.getResponseCode() == 200) {
            try (InputStream is = conn.getInputStream();
                 BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
                StringBuilder sb = new StringBuilder();
                String line;
                while ((line = br.readLine()) != null) sb.append(line);
                String resp = sb.toString();
                // 假設響應包含 "200" 表示成功
                return resp.contains("200") ? "SUCCESS" : "FAIL";
            }
        }
        return "HTTP_ERROR_" + conn.getResponseCode();
    }
}

步驟 2:使用 ExecutorService 批量提交並收集結果

public class SmsBatchSender {
    public static int sendBatch(List<String> phones, String templateName) {
        ExecutorService executor = Executors.newFixedThreadPool(10); // 固定線程池,避免 OOM
        List<Future<String>> futures = new ArrayList<>();

        // 提交所有任務
        for (String phone : phones) {
            Callable<String> task = new SmsTask(templateName, phone);
            futures.add(executor.submit(task));
        }

        // 收集結果
        int successCount = 0;
        for (Future<String> future : futures) {
            try {
                String result = future.get(10, TimeUnit.SECONDS); // 設置超時,防止卡死
                if ("SUCCESS".equals(result)) successCount++;
            } catch (TimeoutException e) {
                future.cancel(true); // 中斷卡住的任務
                log.warn("發送超時: {}", e.getMessage());
            } catch (ExecutionException e) {
                log.error("任務異常: {}", e.getCause().getMessage());
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt(); // 保留中斷狀態
                break;
            }
        }

        executor.shutdown(); // 關閉線程池
        return successCount;
    }
}

進階選型:CompletableFuture (JDK 8+)

若業務涉及多任務編排(如串行、並行、聚合、異常處理),強烈推薦使用 CompletableFuture,支持鏈式調用、異步回調、組合操作(thenCompose、thenCombine、allOf、anyOf),代碼更聲明式、可讀性更強。

// 異步發送並統計成功數(簡化版)
List<CompletableFuture<String>> futures = phones.stream()
    .map(phone -> CompletableFuture.supplyAsync(() -> sendSms(phone), executor))
    .collect(Collectors.toList());

long success = futures.stream()
    .map(CompletableFuture::join) // 等待所有完成
    .filter("SUCCESS"::equals)
    .count();

常見坑點提醒

  1. 線程池未關閉:必須調用 shutdown() 或 shutdownNow(),否則 JVM 無法正常退出。
  2. Future.get() 無超時風險:生產環境務必使用帶超時的 get(timeout, unit),防止下游服務無響應導致調用線程永久阻塞。
  3. 異常吞噬:ExecutionException 包裹了真實異常,日誌務必打印 e.getCause()。
  4. 大量任務堆積:使用有界隊列的線程池(new ThreadPoolExecutor(..., new LinkedBlockingQueue<>(1000), ...))並配置拒絕策略,避免 OOM。

總結

告別 Runnable 無返回值的限制,擁抱 Callable + Future 標準化異步編程模型。配合線程池隔離資源、超時控制、異常捕獲,構建健壯高效的並發系統。JDK 8 以上項目優先考慮 CompletableFuture 實現複雜編排,讓異步代碼像同步代碼一樣易讀易維護。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved