轉眼間兩個月未更新部落格。一方面覺得需學習的技術太多,寫經驗分享不如多讀幾本技術書;另一方面網路上已有大量成熟中文文章,重複造輪子意義不大。既然決定寫,就希望能提供一些不同視角與啟發。
使用成熟框架開發併發應用,不僅能節省建構專案時間,更能提升應用效能。手動實現的併發應用不僅耗時,效能往往也難以達標。
以曾開發的多執行緒下載器為反例:
Atomic 套件利用 CAS 更新資料,無需加鎖(但執行緒空轉仍存在)
這是一種工作單元與執行機制的解耦。HotSpot VM 中 Java 執行緒一對一映射為本地 OS 執行緒。
為充分利用多核處理器,需提升應用本身並行能力。Fork/Join 將任務遞迴拆分為子任務,執行後合併結果。雖類似 MapReduce,但實際效能遠不如 Executor,遞迴棧超過十層時 JVM 甚至會卡死或崩潰,故不再深究。
最常用實現,搭配 Runtime.getRuntime().availableProcessors() 分配處理器核心數的執行緒。內部使用無界 LinkedBlockingQueue,任務可能無限積壓,建議監控 size() 防止隊列無限增長。
等價於 Executors.newFixedThreadPool(1),保證任務循序執行。
使用 SynchronousQueue(無容量隊列),offer 必須對應 poll。工作執行緒飽和時會阻塞,類似 Exchanger 但支援多生產者/消費者。
內部使用 DelayQueue(優先級隊列封裝),時間早的任務優先級高。用於延遲執行或定期執行任務,功能類似 Timer 但更靈活,支援多後台執行緒。
Future.get() 取回結果可能阻塞,最壞情況下非同步退化為同步。Guava 提供 ListenableFuture,採用觀察者模式,非同步計算完成後立即執行 addListener 指定的 Runnable,實現「完成即通知」。
教學資源:https://ifeve.com/google-guava-listenablefuture/
public class ExecutorDemo {
public static void main(String[] args) {
ExecutorService single = Executors.newSingleThreadExecutor();
Callable<String> callable = Executors.callable(new Runnable() {
@Override public void run() {
for (int i = 0; i < 100; i++) {
try { System.out.println(i); }
catch (Throwable e) { e.printStackTrace(); }
}
}
}, "success");
Future<String> f = single.submit(callable);
try {
System.out.println(f.get());
single.shutdown();
} catch (Throwable e) { e.printStackTrace(); }
}
}