Future 模式是異步編程的經典設計模式,核心思想:調用者立即拿到「提貨單」,背後啟動線程生產「真實貨物」,調用者稍後憑單取貨。本文基於經典「蛋糕店」案例,從角色協作、源碼流程、同步機制、擴展方向四個維度深度剖析。
一、角色劇本:蛋糕店比喻
| 角色 | 比喻 | 代碼類 | 職責 |
| Client | 顧客 | Main | 下單 → 拿提貨單 → 幹別的 → 憑單取蛋糕 |
| Host | 蛋糕店老闆 | Host | 接單 → 開提貨單 → 派徒弟做蛋糕 → 回單給顧客 |
| FutureData | 提貨單 | FutureData | 佔位符,同步等待真實蛋糕就緒 |
| RealData | 真實蛋糕 | RealData | 耗時構建的最終產物 |
二、完整源碼流程追蹤
1. 客戶下單
public class Main {
public static void main(String[] args) {
Host host = new Host();
Data d1 = host.request(10, 'A'); // 立即返回 FutureData
Data d2 = host.request(20, 'B');
Data d3 = host.request(30, 'C');
// 做其他事...
try { Thread.sleep(200); } catch (InterruptedException ignored) {}
// 取貨:阻塞直到就緒
System.out.println(d1.getContent());
System.out.println(d2.getContent());
System.out.println(d3.getContent());
}
}
2. 老闆接單
public class Host {
public Data request(final int count, final char c) {
// (1) 造提貨單
final FutureData future = new FutureData();
// (2) 派徒弟(新線程)做蛋糕
new Thread(() -> {
RealData real = new RealData(count, c); // 耗時操作
future.setRealData(real); // 填充提貨單
}).start();
// (3) 立即回單
return future;
}
}
3. 徒弟做蛋糕
public class RealData implements Data {
private final String content;
public RealData(int count, char c) {
char[] buf = new char[count];
for (int i = 0; i < count; i++) {
buf[i] = c;
try { Thread.sleep(1000); } catch (InterruptedException ignored) {} // 模擬耗時
}
content = new String(buf);
}
public String getContent() { return content; }
}
4. 提貨單內部機制
public class FutureData implements Data {
private RealData realData;
private boolean ready = false;
public synchronized void setRealData(RealData rd) {
if (ready) return; // 冪等
this.realData = rd;
ready = true;
notifyAll(); // 喚醒所有等待線程
}
public synchronized String getContent() {
while (!ready) { // while 防虛假喚醒
try { wait(); } catch (InterruptedException ignored) {}
}
return realData.getContent();
}
}
三、核心同步機制圖解
時間軸:
Client主線程: request() ──────▶ 其他工作 ──────▶ getContent() [WAIT] ──────▶ 返回結果
│ ▲
▼ │
Host請求線程: new FutureData() │
│ │
▼ │
徒弟線程: RealData構造(慢) ──▶ setRealData() ──▶ notifyAll()
│
└── 唤醒 Client 主線程
關鍵點
synchronized 保證 ready 可見性與原子性。
while (!ready) wait() 模式防虛假喚醒、防重複通知。
notifyAll() 而非 notify():避免多條件等待時喚醒錯誤線程(此處單條件可用 notify)。
- 每個請求獨立線程、獨立 FutureData,客戶間無競爭,隔離性好。
四、與 JDK FutureTask/CompletableFuture 映射
| 手工模式 | JDK 對應 | 備註 |
| FutureData | FutureTask / CompletableFuture | 實現 Future 接口,提供 get()、cancel()、isDone() |
| RealData 構造邏輯 | Callable / Supplier | 任務邏輯封裝 |
| new Thread(...).start() | ExecutorService.submit() | 線程池管理、併發控制 |
| setRealData() | FutureTask.run() 內部 set() | 自動完成設置 |
五、擴展增強方向
- 超時獲取:
getContent(long timeout, TimeUnit unit) → wait(timeout),超時拋 TimeoutException。
- 取消支持:添加
cancelled 標誌,getContent() 檢查拋 CancellationException,徒弟線程定期檢查 Thread.interrupted() 停止工作。
- 進度回調:
FutureData.addProgressListener(ProgressListener l),徒弟線程周期性通知進度。
- 異常傳播:
setException(Throwable) 存儲異常,getContent() 重新拋出(包裝為 ExecutionException)。
- 併發合併:
allOf(futures...)、anyOf(futures...) 等待多個提貨單。
六、常見誤區
- 以為 FutureData 就是異步:它只是同步等待的協調器,異步性來自獨立線程構建 RealData。
- 忽略 while 循環:直接
if (!ready) wait() 會導致虛假喚醒後邏輯錯誤。
- 共享 FutureData:多客戶共用一個提貨單會導致數據錯亂,每請求一個 FutureData。
- 在 RealData 構造器中拋異常未處理:導致
setRealData 永不調用,客戶永久阻塞。必須 try-catch 並調用 setException。
Future 模式以極簡的同步原語實現了生產者-消費者解耦,是理解 FutureTask、CompletableFuture、Actor 模型、Reactive Streams 等高級異步框架的基石。建議手寫一遍本文代碼,體會 wait/notify 與狀態機的協舞之美。
- 上一篇文章: Java 異步回調實戰:Future 模式、CompletableFuture 與自定義回調接口
- 下一篇文章: Java 多線程實現異步調用:Future 模式完整實戰與線程池優化
Copyright ©
程式碼之家 All Rights Reserved