擴展性是系統以優雅方式處理增長工作負載的能力,或以明顯方式擴充資源提升吞吐量。垂直擴展指單節點增加資源(CPU、記憶體);水平擴展指增加節點(如從 1 台 Web 伺服器擴至 3 台)。隨著硬體成本降低,叢集已取代超級電腦成為高效能運算主流。
許多開發者視效能為事後補救,企圖靠昂貴硬體解決。但實驗室案例顯示:某產品在 8 CPU 機器上效能反而不如 4 CPU。根因在於應用無法充分利用 CPU 資源,部分應用在 8 CPU 上僅佔用 20% 算力。
Java 中 `synchronized` 關鍵字雖方便,但成本高於其他平台同步機制。即使單執行緒,同步方法調用也比非同步慢。若大量執行緒競爭同一監視器,僅一執行緒能進入同步區塊,其餘進入 BLOCKED 狀態,CPU 閒置,增加 CPU 無濟於事。熱鎖還會導致更多執行緒切換與系統呼叫,JVM 需維護競爭佇列,消耗大量時間在 JVM/OS 代碼而非業務邏輯上。
診斷熱鎖:發送 `QUIT` 訊號(Linux: `kill -3 PID`)獲取執行緒堆疊,若見大量 `waiting for monitor entry` 即為熱鎖問題。
對於簡單操作(如計數器遞增),`synchronized` 過於重量級。Java 5.0 引入硬體級 CAS(Compare-And-Swap)操作,`java.util.concurrent.atomic` 套件提供 9 種原子變數類別(`AtomicInteger`、`AtomicLong`、`AtomicReference` 等),實現 wait-free/lock-free 算法。實驗室案例:將 `Vector` 替換為 `ConcurrentHashMap`,8 核 CMT 機器上效能提升 3 倍以上。`ConcurrentLinkedQueue` 採用原子變數直接實作 wait-free,`ConcurrentHashMap` 則用 `ReentrantLock`(基於原子變數維護等待佇列)。
缺少同步導致競爭條件,兩執行緒同時修改共享資源破壞資料結構。實驗室某 ERP 在 128 核機器上 CPU 90% 卻吞吐量極低,OS 追蹤發現所有 CPU 卡在 `HashMap.get()` 死循環。根因:未保護的共享 `HashMap` 內部結構被破壞導致 `e.next` 永不為 null。Sun 不視為 Bug,建議改用 `ConcurrentHashMap`。構建可擴展系統時應納入規範。
Java 1.4 `java.nio` 引入非阻塞 I/O,主流框架(Apache Mina、Sun Grizzly)廣泛應用。實驗室對比 Tomcat 5(阻塞 I/O)與 GlassFish(非阻塞 I/O):

核心差異:阻塞 I/O 為每請求分配一工作執行緒,處理全程持有執行緒;外部呼叫(DB、檔案、訊息)導致執行緒長時間阻塞,需大量執行緒應對高併發,造成 JVM/OS 維護開銷大、Heap 壓力大、頻繁 GC。非阻塞 I/O 中工作執行緒不綁定特定請求,請求阻塞時執行緒可服務其他請求,幾十個執行緒即可處理數千併發,限制執行緒資源反而獲得更好擴展性(這也是 Tomcat 6 採用非阻塞 I/O 的原因)。
實驗室測試某 ERP 複雜報表生成,單執行緒任務在廉價 AMD 雙核上效能超越昂貴 8 CPU SPARC。原因:單執行緒僅用單核,無法擴展至多處理器。解決方案:任務分解並行化。將報表生成拆解為 12 個月度報表並行生成,再彙總,效能在 4 CPU SPARC 上提升 80%+。
工具輔助:JOMP(基於 OpenMP 指示的預編譯並行 API)、Parallel Java(透過 API 呼叫實現並行,無需預編譯,支援多節點擴展)。
記憶體不足導致頻繁 GC;足夠記憶體減少 GC、增加快取。但過大 Heap 反害擴展性:實驗室 64 位 JVM 電信系統分配 12GB Heap,啟動快取 300 萬物件,首小時極快,隨後 GC 停頓 30+ 分鐘。原因:大量存活物件導致 GC 掃描耗時極長。
以記憶體為中心的應用特徵:請求需大量複雜物件、Session 存放過多物件、Session timeout 過長、執行緒池/EJB 池/物件池過大、快取過大。此類應用難以擴展,併發增長時記憶體暴增,GC 耗時不可接受。
建議:大多數場合 `-Xmx3G` 足夠(32 位系統上限 2G)。多餘記憶體留給 OS 做檔案快取。即時 JVM (JSR-001) 允許開發者控制記憶體回收,期待免費 JVM 採用。若記憶體確為瓶頸,需採用多 JVM 或多機器節點。
解決這些問題需 DBA、系統工程師、網路分析師跨領域協作。