ScopedValue 是 Java 25 中引入的一個 API(JEP 429),它是一種能夠在特定作用域內共享不可變數據的新機制,主要用於在線程內和跨線程之間安全、高效地傳遞數據。

它的核心思想是“作用域”(Scope)。一個 ScopedValue 的值被綁定到一個動態定義的作用域上。一旦執行流程退出這個作用域,該綁定就會被自動撤銷。這個作用域通常由 runWhere(ScopedValue, value, Runnable) 方法來定義。

// 1. 聲明一個 ScopedValue(通常為 static final)
final static ScopedValue<String> USER_CONTEXT = ScopedValue.newInstance();

// 2. 在某個作用域內"綁定"一個值,並運行代碼
ScopedValue.runWhere(USER_CONTEXT, "CLAY", () -> {
    // 在這個 Lambda(作用域)內,USER_CONTEXT 的值是 "CLAY"
    methodThatReadsUserContext();
});

// 3. 在作用域內的任何地方讀取值
void methodThatReadsUserContext() {
    String user = USER_CONTEXT.get(); // 獲取到 "CLAY"
    System.out.println("user is: " + user);
}
// 退出 runWhere 的代碼塊後,USER_CONTEXT 與 "CLAY" 的綁定自動失效

ScopedValue 被設計用來解決 ThreadLocal 的一些長期存在的問題的,相比 ThreadLocal 他有幾個優勢:

ScopedValue 被設計用來解決 ThreadLocal 的一些長期存在的問題的,相比 ThreadLocal 他有幾個優勢:

解決內存泄漏問題

  • ThreadLocal 你需要手動管理其生命週期。調用 set(value) 後,你必須記得在 finally 塊中調用 remove(),否則數據會一直留在線程中,導致內存泄漏(尤其是在線程池中,線程會被複用)。
  • ScopedValue 的生命週期嚴格限定在 runWhere 方法定義的作用域內。退出作用域後,綁定自動地被清除,無需手動移除,從根本上避免了內存泄漏的風險。

虛擬線程更友好

  • ThreadLocal 的內部使用線程內部的 ThreadLocalMap,也就意味着每個 Thread 內部維護了一個 ThreadLocalMap。對於平台線程,因為不會太多,所以還可以接受,但是對於虛擬線程,那可能是成千上萬的,那麼有這麼多個 Map,就顯得笨重了。
  • ScopedValue 的值不存儲在線程中,而是存儲在作用域中。一個 ScopedValue 的綁定在其作用域內(即 runWhere 方法定義的代碼塊內)對所有在該作用域中運行的代碼(包括所有子任務)都是可見的。

父子線程間傳遞更友好

  • ThreadLocal 本身不支持主子線程之間的傳遞,需要藉助 InheritableThreadLocal 實現
  • 在一個 ScopedValue.runWhere 作用域內,使用 StructuredTaskScope.fork()(結構化併發,這個特性在 JDK25 中還是預覽版)創建的所有子任務(虛擬線程)自動就能夠讀取到父作用域中綁定的 ScopedValue。

性能更好

  • ThreadLocal 內部使用線程內部的 ThreadLocalMap,這是一個哈希表結構。get() 和 set() 操作涉及哈希查找,雖然很快,但在極端高性能場景下仍有開銷。
  • JVM 可以將 ScopedValue 的讀取優化為類似局部變量訪問的速度,因為它基於不可變且範圍限定的數據。