具代表性的面試主題

Java 25 Scoped Values:面試如何比較 ThreadLocal 與結構化作用域?

程式題困難
Offer.cc 編輯團隊發佈 更新

題幹

Java 25 的 ScopedValue 解決了什麼問題?請比較它與 ThreadLocal,並說明線上程池或虛擬執行緒中的正確用法。

題幹與適用情境

面試官可能會問:「Java 25 的 ScopedValue 解決了什麼問題?請比較它與 ThreadLocal,並說明線上程池或虛擬執行緒中的正確用法。」

JEP 506 在 JDK 25 將 Scoped Values 定稿。它讓呼叫方在詞法作用域內向深層方法和子執行緒共享不可變資料,減少為傳遞上下文而層層增加參數的需要。題目考察生命週期、可見性和並發邊界,不能只回答「ThreadLocal 的新名字」。

面試官考察點

  • 是否理解 ScopedValue 是不可變、受作用域控制的隱式參數。
  • 是否能說明 where(...).run(...)call(...) 的繫結範圍和恢復行為。
  • 是否識別它與可變 ThreadLocal、執行緒池重用和虛擬執行緒繼承的差異。
  • 是否考慮 key 的存取控制、繫結數量、例外傳播和取消。
  • 是否知道何時仍應保留 ThreadLocal 或顯式參數。

回答前需要釐清的問題

  • 共享的是請求 ID、認證主體、租戶,還是可變交易狀態?
  • 資料必須唯讀嗎,子工作是否需要繼承?
  • 執行模型是平台執行緒、虛擬執行緒、結構化並發,還是現有執行緒池?
  • 程式碼是否可能在作用域外讀取 key,或把 Carrier 洩露給不受信呼叫方?
  • 現有框架是否依賴 ThreadLocal 的清理、可變更新或 MDC 整合?

30 秒回答架構

可以這樣回答:

ScopedValue 是 Java 25 的不可變、詞法作用域上下文機制。呼叫方用 ScopedValue.where(key, value).run(...)call(...) 建立繫結,作用域結束後自動恢復;只有持有 key 的程式碼能讀取它。相比 ThreadLocal,它減少執行緒池清理和可變狀態洩漏,適合請求上下文、虛擬執行緒和結構化並發。它不取代需要逐步更新的 ThreadLocal,也不能跨出作用域保存參照。實作前我會驗證子工作繼承、例外和取消行為,並控制 key 的存取權限。

分步深入解答

先理解繫結模型

ScopedValue 的值在動態執行範圍內可讀,但繫結由程式結構明確界定:

java
static final ScopedValue<String> REQUEST_ID = ScopedValue.newInstance();

ScopedValue.where(REQUEST_ID, "req-42").run(() -> {
    audit("start");
    handleRequest();
});

static void audit(String message) {
    logger.info("{} {}", REQUEST_ID.orElse("missing"), message);
}

audit 不必接收請求 ID 參數,但只有在繫結作用域中才能讀取有意義的值;作用域結束後,繫結不會繼續污染呼叫執行緒。

對比 ThreadLocal 的可變性

ThreadLocal 允許每條執行緒持有可變值,線上程池重用時必須清理,否則下一個請求可能讀到舊上下文。ScopedValue 的設計目標是共享不可變值,並透過巢狀繫結覆蓋外層值,再在作用域退出後恢復。它減少清理責任,但不能承載需要在深層程式碼中反覆 set 的狀態機。

處理子執行緒與虛擬執行緒

JEP 506 關注與虛擬執行緒和結構化並發配合的成本與可推理性。是否自動繼承、繼承何時發生以及任務執行器的行為必須以目標 JDK 和 API 契約驗證,不能把普通執行緒池的經驗直接套用。共享物件本身仍應不可變,避免透過參照繞過上下文邊界。

設計 key、例外和相容路徑

key 應是私有 static final 物件或受控 API 的一部分,不要把可被任意模組持有的全域 key 當作安全邊界。讀取缺失值時選擇 orElse 或顯式例外;orElse 的參數在 Java 25 不能是 null。例外離開 runcall 時,作用域會結束並恢復外層繫結。需要相容舊 JDK 時保留顯式參數或 ThreadLocal 實作,並用測試矩陣驗證行為。

高品質示範回答

我會把 ScopedValue 視為不可變的隱式參數,而不是新的可變 ThreadLocal。請求入口用 where(key, value).runcall 建立結構化作用域,深層方法透過持有的 key 讀取請求 ID、租戶或認證主體;退出作用域後繫結自動恢復。它適合虛擬執行緒和結構化並發中的唯讀上下文,避免執行緒池重用造成的清理遺漏。ThreadLocal 仍適合必須逐步更新、且框架已圍繞它建立的狀態,但要在 finally 中清理。實作時我會限制 key 可見性,驗證子工作繼承、例外、取消和巢狀覆蓋,並在舊 JDK 上保留顯式參數或相容實作。

常見錯誤

  • 把 ScopedValue 說成自動清理的可變 ThreadLocal。
  • 在作用域外保存 Carrier、值或讀取 key,導致生命週期錯誤。
  • 忽略執行緒池重用、子工作繼承和虛擬執行緒差異。
  • 使用可變物件作為值,卻宣稱整個上下文不可變。
  • 讓任意模組共享 key,把存取控制誤當成資料加密。
  • 忘記 Java 25 orElse 不接受 null,或沒有舊 JDK 相容策略。

追問及應對

1. ScopedValue 能取代所有 ThreadLocal 嗎?

不能。它適合唯讀、作用域明確的上下文;需要在呼叫鏈中逐步更新的狀態仍可使用 ThreadLocal 或顯式參數,並承擔清理責任。

2. 巢狀繫結會發生什麼?

內層作用域可以暫時繫結同一 key 的新值,內層退出後恢復外層值。回答時應說明例外路徑也必須退出作用域,不能把內層值洩露到外層。

3. 如何測試並發繼承?

分別覆蓋平台執行緒、虛擬執行緒、結構化任務和執行緒池重用;檢查子工作看到的值、取消後的清理、巢狀覆蓋和作用域外讀取。測試應固定 JDK 版本與執行器設定。

公開來源

同類題目

相關面試工具

用 Screenshot 處理演算法題

截圖題目後,依序看約束、解法、程式碼、邊界條件和複雜度。

查看工具