在多線程編程中,主線程無法預知子線程何時執行完畢。直接調用 getter 方法往往拿到的是初始值,而非線程處理後的結果。這是由線程調度的隨機性決定的——即便提高線程優先級,也無法保證 100% 先執行完畢。
public class ReturnThreadInfo extends Thread {
private String str = "Hello"; // 初始值
public void run() {
this.str = "Hello World!"; // 線程任務:修改值
}
public String getThreadInfo() {
return this.str;
}
}
public class Main {
public static void main(String[] args) {
ReturnThreadInfo thread = new ReturnThreadInfo();
thread.start(); // 啟動線程
System.out.println(thread.getThreadInfo()); // 輸出 "Hello" 而非預期的 "Hello World!"
}
}
原因分析:主線程執行 `println` 時,子線程的 `run()` 方法大概率尚未執行,導致讀到舊值。
主線程循環調用獲取方法,直到返回值不再是初始標識值。
public class Main {
public static void main(String[] args) {
ReturnThreadInfo thread = new ReturnThreadInfo();
thread.start();
// 輪詢循環
while (true) {
String result = thread.getThreadInfo();
if (!"Hello".equals(result)) { // 判斷是否為初始值
System.out.println(result); // 輸出 "Hello World!"
break;
}
// 建議加入短暫睡眠降低 CPU 佔用
try { Thread.sleep(10); } catch (InterruptedException ignored) {}
}
}
}
| 優點 | 缺點 |
|---|---|
| 實現簡單,無需修改線程類 | 忙等待消耗 CPU 資源 |
| 邏輯直觀,易於理解 | 無法精確控制檢查頻率 |
| 線程數多時性能急劇下降 |
好萊塢原則:「別調用我們,我們會調用你。」線程執行完畢後,主動調用主線程提供的回調方法傳遞結果。
// 主類提供靜態回調方法
public class Main {
public static void main(String[] args) {
ReturnThreadInfo thread = new ReturnThreadInfo();
thread.start(); // 啟動後主線程可繼續做其他事或結束
}
// 回調入口:線程結束時調用
public static void receiveStr(String result) {
System.out.println("收到線程結果: " + result);
}
}
// 線程類在 run() 結束前回調
public class ReturnThreadInfo extends Thread {
private String str = "Hello";
public void run() {
this.str = "Hello World!";
Main.receiveStr(str); // 關鍵:主動通知主線程
}
}
當有多個對象關心線程結果時,定義回調接口實現觀察者模式:
// 1. 定義回調接口
public interface ThreadResultListener {
void onThreadComplete(String result);
}
// 2. 線程類維護監聽器列表
public class ReturnThreadInfo extends Thread {
private List listeners = new ArrayList<>();
private String result;
public void addListener(ThreadResultListener listener) {
listeners.add(listener);
}
public void run() {
this.result = "Hello World!";
// 通知所有監聽者
for (ThreadResultListener listener : listeners) {
listener.onThreadComplete(result);
}
}
}
// 3. 多個業務類實現接口
public class BusinessA implements ThreadResultListener {
public void onThreadComplete(String result) {
System.out.println("業務A處理: " + result);
}
}
public class BusinessB implements ThreadResultListener {
public void onThreadComplete(String result) {
System.out.println("業務B處理: " + result);
}
}
JDK 5+ 提供了更優雅的標準化方案:
// 使用 Callable + FutureTask (JDK 5)
Callable task = () -> {
Thread.sleep(1000); // 模擬耗時操作
return "Hello World!";
};
FutureTask futureTask = new FutureTask<>(task);
new Thread(futureTask).start();
String result = futureTask.get(); // 阻塞獲取結果,自動同步
System.out.println(result);
// 使用 CompletableFuture (JDK 8+,推薦)
CompletableFuture.supplyAsync(() -> {
Thread.sleep(1000);
return "Hello World!";
}).thenAccept(result -> System.out.println("異步結果: " + result));
| 方案 | 適用場景 | 複雜度 | 性能 | 推薦指數 |
|---|---|---|---|---|
| 直接獲取 | 教學演示/單元測試 | 極低 | 不可用 | ⭐ |
| 輪詢 | 簡單任務、無法修改線程類 | 低 | 差 (CPU 浪費) | ⭐⭐ |
| 回調/監聽器 | 事件驅動架構、多消費者 | 中 | 優 | ⭐⭐⭐⭐ |
| Future/Callable | 需要阻塞等待結果的場景 | 中 | 優 | ⭐⭐⭐⭐ |
| CompletableFuture | 現代異步編程、組合操作 | 中高 | 優 | ⭐⭐⭐⭐⭐ |