具代表性的面試主題

程式設計面試題:Java 25 的 ScopedValue 如何安全傳播請求上下文?

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

題幹

Java 25 的 ScopedValue 如何安全傳播請求上下文?

題幹與適用場景

一個 Java 服務需要把 trace ID、租戶或安全主體傳給深層回呼和虛擬執行緒,但不希望每層方法都增加參數,也不希望執行緒池重用造成上下文洩漏。請說明 Java 25 ScopedValue 的詞法作用域、繫結與重繫結、Carrier、虛擬執行緒和結構化並發行為,並給出從 ThreadLocal 遷移的邊界。

面試官考察點

  • 能否解釋隱式上下文的可見範圍和生命週期。
  • 是否理解 ScopedValue 繫結只在 runcallwhere 作用域內可讀。
  • 能否區分不可變快照傳播與可變執行緒本地狀態。
  • 能否處理巢狀繫結、子工作、例外和取消。
  • 能否識別安全主體、可變物件和執行緒池遷移風險。

回答前需要釐清的問題

  • 上下文是請求級唯讀值,還是需要在線程之間寫回的可變狀態?
  • 工作使用虛擬執行緒、結構化並發還是傳統執行緒池?
  • 子工作是否必須繼承上下文,是否允許覆蓋某個鍵?
  • 需要支援哪些 JDK 版本,遷移能否分階段進行?

30 秒回答框架

我會把請求上下文建模為不可變值,用 ScopedValue.where(key, value).runcall 建立詞法作用域。作用域內的深層程式碼透過鍵讀取,離開後自動解除繫結;巢狀作用域可以暫時重繫結,Carrier 本身不可變且執行緒安全。對虛擬執行緒和結構化並發,它比可變 ThreadLocal 更容易約束生命週期,但不適合需要跨作用域寫回的狀態;遷移時保留明確參數和舊實作的邊界測試。

分步驟深入解答

1. 定義上下文鍵

使用私有的 ScopedValue 鍵,把 trace ID、tenant ID 和主體封裝在不可變 Context 中。不要把可變 map 或可寫工作階段物件直接暴露給深層程式碼。

2. 建立詞法作用域

java
static final ScopedValue<RequestContext> REQUEST = ScopedValue.newInstance();

ScopedValue.where(REQUEST, context).run(() -> handle(request));

handle 的深層呼叫可以讀取目前繫結,但作用域結束後值不再可見。這樣生命週期與程式碼區塊繫結,不依賴執行緒何時回收。

3. 讀取與缺省處理

讀取前可用 isBound() 判斷,或用 orElse 提供明確的內部預設值。安全主體等必需上下文應使用 orElseThrow,避免靜默以匿名身份執行。

4. 解釋 Carrier

ScopedValue.Carrier 是不可變且執行緒安全的鍵值映射;連續呼叫 where 會回傳新的 carrier。組裝多個上下文繫結後再呼叫 runcall,不要共享可變 carrier。

5. 處理巢狀重繫結

內層 where 可以為同一個鍵暫時繫結新值,內層結束後外層值恢復。審查時要畫出作用域樹,確認回呼、例外處理和日誌不會讀取錯誤租戶或主體。

6. 配合虛擬執行緒與結構化並發

把工作放在詞法作用域內建立,使子工作能取得約定的上下文快照。取消或例外結束時作用域仍會退出,不需要手動清理執行緒本地變數;仍要明確哪些子工作可以覆蓋鍵。

7. 與 ThreadLocal 比較

ThreadLocal 適合需要執行緒關聯和可變寫入的舊 API,但在線程池重用時容易忘記清理。ScopedValue 傾向唯讀、短生命週期和受限可見性,不能直接取代所有 ThreadLocal,尤其是需要跨回呼修改狀態的情境。

8. 設計遷移與觀測

先定義上下文 schema、允許讀取的模組和 JDK 相容矩陣,再用適配層讓舊程式碼逐步讀取 ScopedValue。記錄未繫結例外、跨執行緒邊界、巢狀覆蓋和請求結束後的物件引用,防止把隱式上下文變成難以追蹤的全域變數。

設計取捨與邊界

ScopedValue 傳遞的是繫結引用;值本身若可變,仍可能產生資料競爭和越權修改。它不能讓任意新執行緒自動獲得業務語義,也不能取代明確參數、認證策略或取消協議。Java 25 API 適合在明確 JDK 基線和結構化並發模型下採用,舊版本需要保留相容方案。

落地計畫與證據

  1. 列出現有 ThreadLocal 的鍵、寫入點、清理點和跨執行緒邊界。
  2. 為唯讀請求上下文建立 ScopedValue key 與不可變型別。
  3. where(...).run/call 包住請求入口和結構化工作建立點。
  4. 測試巢狀重繫結、例外、取消、虛擬執行緒、執行緒池重用和未繫結存取。
  5. 以 Oracle Java 25 API 對 ScopedValueCarrier、存取控制和結構化並發的說明作為發布依據。

常見誤區與追問

誤區一:把 ScopedValue 當作可變全域變數

鍵控制可見性,但繫結物件仍可能可變。應使用不可變 context,並限制寫入操作。

誤區二:忘記作用域邊界

作用域結束後讀取會失敗或回到外層繫結。必須明確入口、回呼和非同步工作的建立位置。

誤區三:所有 ThreadLocal 都直接替換

需要跨作用域寫回或舊 JDK 支援的程式碼仍有不同約束。先分類,再遷移唯讀請求上下文。

誤區四:假設任意執行緒都自動繼承

上下文傳播要和工作建立、執行器和結構化並發策略一起設計,不能只看執行緒名稱。

誤區五:忽略安全主體覆蓋

巢狀重繫結可能改變稽核身份。安全主體應不可變、必需時強制存在,並對覆蓋行為留痕。

公開來源

同類題目

相關面試工具

用 Screenshot 處理演算法題

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

查看工具