在 Java 多線程開發中,Thread 類的 run() 方法返回值類型為 void,無法直接獲取線程執行結果。早期開發者常通過共享變量、回調接口或 Thread.join() 等「曲線救國」方案獲取結果,但這些方式要麼線程不安全、要麼代碼冗餘、要麼阻塞主線程。JDK 5 引入的 Callable 與 Future 機制,從根本上解決了「有返回值的線程」需求。
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
V 表示任務返回值類型。call() 方法可拋出受檢異常,比 run() 更靈活。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() 獲取真實異常。FutureTask 實現了 RunnableFuture 接口,既可作為任務提交給 ExecutorService,又可直接作為 Thread 構造參數。
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();
}
}
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,支持鏈式調用、異步回調、組合操作(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();
shutdown() 或 shutdownNow(),否則 JVM 無法正常退出。get(timeout, unit),防止下游服務無響應導致調用線程永久阻塞。ExecutionException 包裹了真實異常,日誌務必打印 e.getCause()。new ThreadPoolExecutor(..., new LinkedBlockingQueue<>(1000), ...))並配置拒絕策略,避免 OOM。告別 Runnable 無返回值的限制,擁抱 Callable + Future 標準化異步編程模型。配合線程池隔離資源、超時控制、異常捕獲,構建健壯高效的並發系統。JDK 8 以上項目優先考慮 CompletableFuture 實現複雜編排,讓異步代碼像同步代碼一樣易讀易維護。